Fast intersection of HashSetint and Listint
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
The fastest way to intersect a HashSet<int> and a List<int> depends on what you want the result to mean. If you want unique results, use HashSet.IntersectWith. If you want to preserve duplicates from the list, iterate the list and test membership with the hash set's Contains.
Start With The Meaning Of "Intersection"
A HashSet<int> contains unique values. A List<int> can contain duplicates and preserves order.
That means there are two reasonable interpretations:
- set-style intersection, where each output value appears once
- list-filter intersection, where duplicates from the list are preserved
The implementation changes depending on which behavior you need.
Fast Unique Intersection
If you want unique output values, the cleanest solution is to clone the hash set and intersect it with the list.
Why this is fast:
- membership checks in
HashSet<int>are averageO(1) - '
IntersectWithis optimized for set operations' - the output stays unique naturally
For set semantics, this is usually the best answer.
Fast Intersection That Preserves List Duplicates
If you want every matching item from the list, including repeated values, just filter the list using the set:
This returns 3, 3, 4.
That is still efficient because each lookup into the hash set is average O(1), so the total work is linear in the size of the list.
Complexity Discussion
For the duplicate-preserving version:
- iterate the list once
- do one hash lookup per item
That makes the time complexity approximately O(n) where n is the size of the list, assuming the hash set already exists.
For the unique set-style version with IntersectWith, the total cost is still effectively linear in the sizes of the collections involved.
The expensive approach is the wrong one:
or anything that repeatedly calls List.Contains against another list. That pushes lookups toward O(n) each and can easily turn the total work into quadratic behavior.
Choose The Smaller Collection To Hash If Needed
If neither side is already a HashSet<int>, a good general rule is to hash the smaller collection and iterate the larger one. That minimizes memory overhead while keeping membership tests fast.
Example:
That pattern is useful when the inputs are general collections rather than one set and one list.
Be Careful With LINQ Semantics
LINQ makes the code concise, but it does not change the underlying rules. Intersect returns unique results because it uses set semantics:
That result would contain 3, 4, not 3, 3, 4.
So Intersect is fine when uniqueness is what you want, but it is the wrong answer if list duplicates must survive.
Common Pitfalls
The biggest mistake is not deciding whether duplicates should be preserved. That semantic choice determines the correct implementation.
Another mistake is using List.Contains repeatedly when a hash lookup is available. That can turn a simple task into a slow quadratic scan.
People also forget that LINQ Intersect removes duplicates. It is a set operation, not a duplicate-preserving filter.
Finally, do not mutate the original hash set with IntersectWith unless that is intentional. Clone it first if the original set must remain unchanged.
Summary
- Use
HashSet.IntersectWithfor a fast unique intersection. - Use
list.Where(set.Contains)when you need to preserve duplicates from the list. - Hash lookups are average
O(1), which keeps these solutions efficient. - '
Enumerable.Intersectuses set semantics and removes duplicates.' - Decide the output semantics first, then choose the implementation that matches them.

