HashSet
HashSet<T> stores unique elements with O(1) Contains — ideal for deduplication, visited tracking, and set algebra (Union, Intersect, Except).
Introduction
HashSet<T> stores unique elements with O(1) Contains — ideal for deduplication, visited tracking, and set algebra (Union, Intersect, Except). Unlike Dictionary, no value payload — only keys in a set semantics.
Interview graph problems use HashSet for cycle detection. Enterprise apps use HashSet for permission sets and tag collections.
The story
An enterprise admin portal checks whether a user holds all permissions required to approve a payroll run. Permissions are stored in a case-insensitive set for O(1) lookups, and auditors sometimes need to see which permissions two roles share — a set intersection operation.
Understanding the topic
Key concepts
- Uniqueness enforced on Add.
- Add returns false if duplicate.
- Set operations: UnionWith, IntersectWith, ExceptWith.
- No indexing — enumeration order undefined.
- Hash-based like Dictionary without values.
- FrozenSet for immutable read-heavy (.NET 8).
Step-by-step explanation
- Add inserts if not present.
- Contains checks membership O(1) average.
- Remove deletes element.
- Set operations modify in place or LINQ ToHashSet.
- ExceptWith removes elements in other set.
- Comparer optional for custom equality.
Practical code example
Permission checker with HashSet and set intersection:
namespace TechLearningPro.HashSet;public sealed class PermissionService{private readonly HashSet<string> _userPermissions;public PermissionService(IEnumerable<string> permissions) =>_userPermissions = permissions.ToHashSet(StringComparer.OrdinalIgnoreCase);public bool HasAll(IEnumerable<string> required) =>required.All(p => _userPermissions.Contains(p));public HashSet<string> CommonWith(IEnumerable<string> other) =>_userPermissions.Intersect(other, StringComparer.OrdinalIgnoreCase).ToHashSet(StringComparer.OrdinalIgnoreCase);}
Line-by-line code explanation
HashSet<string> _userPermissionsstores unique permission strings with fast membership tests.permissions.ToHashSet(StringComparer.OrdinalIgnoreCase)builds the set ignoring case differences.HasAll(IEnumerable<string> required)checks that every required permission is present.required.All(p => _userPermissions.Contains(p))uses LINQ with O(1) Contains per permission.CommonWith(IEnumerable<string> other)finds permissions shared with another collection._userPermissions.Intersect(other, StringComparer.OrdinalIgnoreCase)computes the set intersection..ToHashSet(StringComparer.OrdinalIgnoreCase)materializes the result as a new case-insensitive set.
Key takeaway: StringComparer.OrdinalIgnoreCase for case-insensitive permissions. Intersect returns shared permissions — useful for audit UI.
Real-world use
Where you'll use this in production
- RBAC permission checks.
- Deduplicating email recipient lists.
- Graph visited nodes in BFS/DFS.
- Tag filtering on product catalogs.
Best practices
- Use HashSet when need uniqueness + fast Contains.
- Specify StringComparer for case rules.
- ToHashSet() materializes LINQ distinct efficiently.
- FrozenSet when set never changes after build.
- Prefer over List for membership-heavy loops.
Common mistakes
- Using List.Contains in loop — O(n²).
- Assuming stable iteration order.
- Forgetting comparer — case sensitivity bugs.
- Mutating elements affecting hash after Add.
Advanced interview questions
Q1BeginnerHashSet vs List?
Q2BeginnerAdd return value meaning?
Q3IntermediateDetect cycle in linked list?
Q4IntermediateUnion vs Concat?
Q5AdvancedFind duplicate in stream of 1M ints memory-efficient?
Summary
HashSet enforces uniqueness with fast membership tests. Set algebra operations simplify permission and tag logic. Replace List.Contains hot paths with HashSet. FrozenSet optimizes immutable read scenarios. Next: Queue and Stack FIFO/LIFO structures.