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
Interview Prep
Practice concise answers, then expand each card for the explanation.
1BeginnerQuestionHashSet vs List?+
Answer
2BeginnerQuestionAdd return value meaning?+
Answer
3IntermediateQuestionDetect cycle in linked list?+
Answer
4IntermediateQuestionUnion vs Concat?+
Answer
5AdvancedQuestionFind duplicate in stream of 1M ints memory-efficient?+
Answer
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.