Concurrent HashSet
.NET Framework
Thread Safety
C# Collections
System.Collections.Concurrent

Concurrent HashSetT in .NET Framework?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

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 byte or bool

The value is not meaningful. It just gives the dictionary something to store.

The Standard ConcurrentDictionary Approach

A minimal wrapper looks like this:

csharp
1using System.Collections.Concurrent;
2using System.Collections.Generic;
3
4public sealed class ConcurrentHashSet<T>
5{
6    private readonly ConcurrentDictionary<T, byte> _items;
7
8    public ConcurrentHashSet() : this(null) { }
9
10    public ConcurrentHashSet(IEqualityComparer<T>? comparer)
11    {
12        _items = new ConcurrentDictionary<T, byte>(comparer ?? EqualityComparer<T>.Default);
13    }
14
15    public bool Add(T item) => _items.TryAdd(item, 0);
16
17    public bool Remove(T item) => _items.TryRemove(item, out _);
18
19    public bool Contains(T item) => _items.ContainsKey(item);
20
21    public int Count => _items.Count;
22}

This gives you atomic add and remove behavior with thread-safe lookup.

Example Use from Multiple Threads

csharp
1using System;
2using System.Threading.Tasks;
3
4public class Program
5{
6    public static void Main()
7    {
8        var set = new ConcurrentHashSet<int>();
9
10        Parallel.For(0, 1000, i =>
11        {
12            set.Add(i % 100);
13        });
14
15        Console.WriteLine(set.Count);
16        Console.WriteLine(set.Contains(42));
17    }
18}

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.

csharp
1using System.Collections.Generic;
2
3public sealed class LockedHashSet<T>
4{
5    private readonly object _gate = new object();
6    private readonly HashSet<T> _set = new HashSet<T>();
7
8    public bool Add(T item)
9    {
10        lock (_gate)
11        {
12            return _set.Add(item);
13        }
14    }
15
16    public bool Contains(T item)
17    {
18        lock (_gate)
19        {
20            return _set.Contains(item);
21        }
22    }
23}

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.

csharp
1var snapshot = new List<int>();
2foreach (var key in setSnapshotSource)
3{
4    snapshot.Add(key);
5}

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
Course
Intermediate
27 lessons
14 hours
OOD Fundamentals

Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.

View the course
Track 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.

Browse interview questions

All Rights Reserved.