Concurrent HashSetT in .NET Framework?
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.
Introduction
The .NET Framework has ConcurrentDictionary, ConcurrentQueue, ConcurrentBag, and other thread-safe collections, but it does not ship with a built-in ConcurrentHashSet<T>. If you need set semantics with concurrent access, the usual answer is to build on top of ConcurrentDictionary<TKey, TValue> or to protect a normal HashSet<T> with your own locking strategy.
The right choice depends on what operations you need. If your workload is mostly add, remove, and contains checks, a dictionary-backed set is usually the simplest and safest option.
Why There Is No Built-In ConcurrentHashSet<T>
A hash set is essentially a key-only hash table. ConcurrentDictionary<TKey, TValue> already solves most of the concurrency problem, so the framework did not add a separate concurrent set type in the same way it added queue or bag types.
That means a practical concurrent set can be modeled as:
- dictionary key = set item
- dictionary value = placeholder such as
byteorbool
The value is not meaningful. It just gives the dictionary something to store.
The Standard ConcurrentDictionary Approach
A minimal wrapper looks like this:
This gives you atomic add and remove behavior with thread-safe lookup.
Example Use from Multiple Threads
Because duplicate keys are ignored by TryAdd, the final count reflects unique items, not insertion attempts.
When a Lock Around HashSet<T> Is Better
A dictionary-backed set is not always the best design. If you need more complex multi-step operations or if several actions must be performed as one atomic block, a normal HashSet<T> with a lock can be easier to reason about.
This can be a better fit if the API needs stronger consistency guarantees than independent concurrent dictionary operations naturally provide.
Enumeration Semantics Matter
This is one area people often skip. Enumerating a concurrent collection while other threads are mutating it is not the same as taking a stable snapshot of a normal HashSet<T>.
With a ConcurrentDictionary-backed set, enumeration is thread-safe, but it may represent a moment-in-time view that does not reflect a single global lock-protected state across the entire iteration.
If your code needs a stable snapshot, copy the items first.
Or expose a snapshot method that materializes the current keys into a separate list or array.
Alternatives Beyond Mutable Concurrent Sets
Sometimes the best answer is neither a custom concurrent set nor a locked HashSet<T>. Other options include:
- '
ImmutableHashSet<T>with atomic reference replacement' - channel or queue-based ownership so only one thread mutates the set
- a normal
HashSet<T>used only on one actor or scheduler
Those designs can be simpler if the application already has a higher-level concurrency model.
Common Pitfalls
The biggest mistake is assuming HashSet<T> itself becomes safe just because reads are common and writes are rare. It does not. Another is wrapping ConcurrentDictionary but then exposing multi-step operations that are not truly atomic as a whole. Developers also often forget about enumeration behavior and assume a concurrent collection provides a perfectly stable iteration snapshot. Finally, if equality semantics matter, do not forget to pass the appropriate comparer into the underlying dictionary.
Summary
- .NET Framework does not include a built-in
ConcurrentHashSet<T>. - The usual implementation uses
ConcurrentDictionary<T, byte>as the backing store. - A locked
HashSet<T>can be better when you need stronger multi-step consistency. - Enumeration of concurrent collections is safe but not necessarily a stable snapshot.
- Choose the concurrency model that matches the operations you actually need, not just the collection name you wish existed.
Related reading
- Concurrent JUnit testing
- Concurrent LRU cache implementation
- Concurrent Synchronous Request-Reply with JMS/ActiveMQ - Patterns/Libraries?
- Concurrent vs serial queues in GCD
- Conditional Validation using Fluent Validation
- Conditionally use 32/64 bit reference when building in Visual Studio
- Concurrent vs serial queues in GCD
- Concurrent write with OCaml Async

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.