Concurrent Dictionary Correct Usage
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
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:
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:
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:
For compare-and-swap style logic, TryUpdate is useful:
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:
This is risky if LoadFromDatabase() has side effects or is very expensive:
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
ContainsKeyplus assignment instead ofGetOrAddorTryAdd. - 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
ConcurrentDictionaryfor shared key-value access under concurrency. - Prefer atomic methods such as
GetOrAdd,TryAdd,TryUpdate, andAddOrUpdate. - 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.

