Thread safe collections in .NET
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.
Introduction
The System.Collections.Concurrent namespace in .NET provides thread-safe collections designed for multi-threaded access without requiring external locks. The main types are ConcurrentDictionary<TKey, TValue>, ConcurrentQueue<T>, ConcurrentStack<T>, ConcurrentBag<T>, and BlockingCollection<T>. These collections use fine-grained locking and lock-free algorithms internally, making them significantly more efficient than wrapping standard collections with lock statements.
Why Standard Collections Are Not Thread-Safe
Standard collections like List<T> and Dictionary<TKey, TValue> are designed for single-threaded access. Concurrent modifications can corrupt internal state, throw exceptions, or produce incorrect results.
ConcurrentDictionary
The most commonly used concurrent collection — a thread-safe hash table:
AddOrUpdate and GetOrAdd are atomic operations. The factory delegate may be called multiple times under contention, but only one result is stored.
ConcurrentQueue
A thread-safe FIFO (first-in, first-out) queue:
ConcurrentQueue<T> is lock-free internally, making it the fastest concurrent collection for producer-consumer scenarios.
ConcurrentStack
A thread-safe LIFO (last-in, first-out) stack:
TryPopRange is more efficient than calling TryPop in a loop because it reduces contention.
ConcurrentBag
An unordered collection optimized for scenarios where the same thread both produces and consumes items:
ConcurrentBag<T> uses thread-local storage internally, making it fastest when each thread adds and removes its own items (e.g., in work-stealing algorithms).
BlockingCollection
A wrapper that adds blocking and bounding to any IProducerConsumerCollection<T>:
BlockingCollection<T> is ideal for producer-consumer patterns. The bounded capacity provides backpressure to prevent producers from overwhelming consumers.
ImmutableCollections (Alternative Approach)
Instead of concurrent mutation, use immutable collections that create new versions on each change:
Choosing the Right Collection
| Scenario | Collection |
| Key-value cache | ConcurrentDictionary<TKey, TValue> |
| Producer-consumer queue | ConcurrentQueue<T> or BlockingCollection<T> |
| Work-stealing pool | ConcurrentBag<T> |
| Undo stack | ConcurrentStack<T> |
| Bounded buffer with backpressure | BlockingCollection<T> |
| Read-heavy, rare writes | ImmutableDictionary<TKey, TValue> |
Common Pitfalls
- Using
lockaround concurrent collections:ConcurrentDictionaryand other concurrent collections handle synchronization internally. Wrapping them inlockstatements adds unnecessary overhead and can cause deadlocks if mixed with the collection's internal locking. - Non-atomic compound operations on ConcurrentDictionary:
if (!dict.ContainsKey(key)) dict.TryAdd(key, value)is not atomic — another thread can add the key between the check and the add. UseGetOrAddorAddOrUpdateinstead, which are atomic. - ConcurrentBag for FIFO ordering:
ConcurrentBag<T>provides no ordering guarantees. If you need items processed in order, useConcurrentQueue<T>. - Forgetting CompleteAdding on BlockingCollection: If the producer never calls
CompleteAdding(), consumers usingGetConsumingEnumerable()block forever. Always callCompleteAdding()in afinallyblock or when the producer finishes. - Assuming Count is accurate: The
Countproperty on concurrent collections is a snapshot that may be stale by the time you use it. Never useif (queue.Count > 0) queue.TryDequeue(...)— useTryDequeuedirectly, as it atomically checks and removes.
Summary
- Use
ConcurrentDictionary<TKey, TValue>for thread-safe key-value operations withGetOrAddandAddOrUpdate - Use
ConcurrentQueue<T>for lock-free FIFO producer-consumer patterns - Use
BlockingCollection<T>when consumers need to block while waiting for items - Use
ConcurrentBag<T>when the same thread produces and consumes items - Avoid locking around concurrent collections — they handle synchronization internally
- Use atomic methods (
TryAdd,TryDequeue,GetOrAdd) instead of check-then-act patterns
Related reading
- Thread safe collections in .NET
- Thread safe Entity Framework 6
- Thread Safe singleton class
- Thread Safety in Python's dictionary
- Thread.Sleep replacement in .NET for Windows Store
- Thread.Sleep vs. Task.Delay when using timeBeginPeriod / Task scheduling
- Thread safety of static blocks in Java
- Thread vs. Threading

OOD Fundamentals
Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.
View the courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.