concurrent dictionary
dictionary usage
multithreading
C# programming
thread safety

Concurrent Dictionary Correct Usage

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

Introduction

ConcurrentDictionary is the right choice when many threads need shared key-value access without a single coarse lock around a normal Dictionary. The most important rule is to use its atomic APIs such as GetOrAdd, TryUpdate, and AddOrUpdate instead of building your own check-then-act sequences around it.

Use Atomic Operations, Not Split Logic

This is the pattern you should avoid:

csharp
1if (!dict.ContainsKey(key))
2{
3    dict[key] = value;
4}

Even with ConcurrentDictionary, that sequence is not one atomic operation. Another thread can modify the dictionary between the check and the assignment.

Prefer the built-in atomic method:

csharp
1using System.Collections.Concurrent;
2
3var dict = new ConcurrentDictionary<string, int>();
4int value = dict.GetOrAdd("retries", 0);

That guarantees the add-or-read behavior happens safely under concurrency.

AddOrUpdate and TryUpdate

When a value must be updated based on the existing value, use the APIs designed for that:

csharp
1using System.Collections.Concurrent;
2
3var counts = new ConcurrentDictionary<string, int>();
4
5counts.AddOrUpdate(
6    "jobs",
7    addValueFactory: _ => 1,
8    updateValueFactory: (_, current) => current + 1
9);

For compare-and-swap style logic, TryUpdate is useful:

csharp
var ok = counts.TryUpdate("jobs", 10, comparisonValue: 9);

This updates the value only if the current value is still 9.

Value Factories May Run More Than Once

One subtle point surprises many developers: the delegate you pass to GetOrAdd or AddOrUpdate can be invoked more than once under contention, even though only one resulting value wins.

That means the factory should be:

  • side-effect free
  • cheap, if possible
  • safe to run more than once

This is correct:

csharp
var cache = new ConcurrentDictionary<string, string>();

string value = cache.GetOrAdd("language", _ => "C#");

This is risky if LoadFromDatabase() has side effects or is very expensive:

csharp
string value = cache.GetOrAdd("user:42", _ => LoadFromDatabase());

You may still need an outer lazy-initialization pattern if the creation cost is significant.

One common pattern is storing Lazy<T> inside the dictionary so the dictionary coordinates key access while Lazy<T> coordinates one-time initialization of the expensive value itself. That keeps the expensive construction logic from being duplicated across competing threads.

Enumeration Is Safe but Not Frozen

Enumerating a ConcurrentDictionary is thread-safe, but you should not treat the enumeration as a perfectly frozen snapshot of future state. Other threads may continue to add or remove entries while you iterate.

That means enumeration is good for:

  • diagnostics
  • metrics
  • approximate reporting

It is not a substitute for a transactional view of shared state.

Keys Must Stay Stable

Just like with a normal dictionary, key equality and hash code must remain stable for as long as the key lives in the collection. Mutable key objects are still a bad idea.

If a key's hash or equality behavior changes after insertion, lookup behavior becomes unpredictable no matter how thread-safe the dictionary implementation is.

Common Pitfalls

  • Using ContainsKey plus assignment instead of GetOrAdd or TryAdd.
  • Assuming value factories run exactly once under contention.
  • Treating enumeration as if it were a strict immutable snapshot.
  • Storing mutable objects as keys whose equality or hash code can change.
  • Adding extra outer locks that defeat the purpose of ConcurrentDictionary.

Summary

  • Use ConcurrentDictionary for shared key-value access under concurrency.
  • Prefer atomic methods such as GetOrAdd, TryAdd, TryUpdate, and AddOrUpdate.
  • Do not build manual check-then-act sequences around dictionary operations.
  • Keep value factories side-effect free because they may execute more than once.
  • Thread-safe access does not remove the need for clear state-management design.

Related reading
Course
Intermediate
27 lessons
15 hours
DSA Fundamentals

Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms