Hash Set
A collection of unique keys backed by a hash table, with expected O(1) add, contains, and remove.
Definition
A hash set stores keys without associated values and guarantees each key appears at most once. It is a Hash Table whose entries are just keys; adding an existing key is a no-op.
The three operations that matter are add, contains, and remove, each expected O(1). Set algebra (union, intersection, difference) runs in O(|A| + |B|).
Typical roles: the visited set in graph traversals, deduplication, "have I seen this before?" checks in one-pass algorithms, and constant-time existence tests that replace an inner loop.
Intuition
A mental model before the formal terms.
A guest list at a door. The bouncer does not scan the list top to bottom; the list is organised so that a name can be checked instantly. Adding a name already on the list changes nothing.
It is a hash map whose values are all "present" — the only question it can answer is yes/no.
How it works
- Hash the key to a bucket.
add(k): if the key is already in the bucket, returnfalse; otherwise insert and returntrue, resizing when the load factor is exceeded.contains(k): scan the bucket for an equal key.remove(k): unlink the key from its bucket.- Set operations iterate one set and probe the other.
Why it works
Uniqueness is enforced at insert time by the equality check inside the bucket; since equal keys hash equally, they always land in the same bucket and are detected.
Expected O(1) follows from the bounded load factor exactly as for the Hash Table.
Operations
| Operation | Description | Cost |
|---|---|---|
| add(k) | Insert if absent; returns whether it was inserted. | O(1) average |
| contains(k) | Membership test. | O(1) average |
| remove(k) | Delete if present. | O(1) average |
| union / intersection / difference | Set algebra with another set. | O(|A| + |B|) |
| size() | Number of distinct keys. | O(1) |
Recognition
How to tell a problem wants this.
- "Contains duplicate", "first unique", "distinct elements", "intersection of two arrays".
- A graph or grid traversal must avoid revisiting nodes (visited set).
- Cycle detection in sequences (happy number, linked list cycle without pointer tricks).
- You need to test existence of
x + k,x - 1, or a complement inO(1).
Interactive demo
Play, step, change the input. ← → and space work too.
Showing the closely related Hash Table visualization.
1h = key % m2insert: scan bucket[h]; if key present: done, else append to the chain3search: scan bucket[h] comparing each key4delete: scan bucket[h]; unlink the matching nodePseudocode
1class HashSet:2 table = HashTable()3 add(k): if table.contains(k): return false; table.put(k, true); return true4 contains(k): return table.contains(k)5 remove(k): return table.remove(k)Implementation
1def demo() -> None:21 · Create and insert3 seen: set[int] = set()4 seen.add(3)5 size_before = len(seen)6 seen.add(3) # no-op: already present7 inserted = len(seen) > size_before # False8 92 · Membership and erase10 has3 = 3 in seen11 seen.discard(3) # remove() would raise if missing12 133 · Deduplicate a list (keeps first occurrence)14 nums = [4, 1, 4, 2, 1]15 unique = list(dict.fromkeys(nums)) # set(nums) alone loses order16 174 · Set algebra (intersection)18 a, b = {1, 2, 3}, {2, 3, 4}19 common = a & b # also a | b, a - b, a ^ b, a <= b20 print(has3, inserted, unique, sorted(common))21 22 23demo()set()creates an empty set ({}would be a dict);addis a no-op for duplicates.discardsilently ignores missing elements;removeraises KeyError.setdoes not preserve order, so order-preserving dedupe usesdict.fromkeys.- Set operators
& | - ^and comparisons<= <implement set algebra directly.
frozensetis hashable, so sets of sets are possible.- Set comprehensions
{f(x) for x in xs}build sets concisely. - Members must be hashable — use tuples for coordinates.
- Writing
s = {}for an empty set. - Expecting
setiteration order to be stable across runs (string hashes are randomized). - Adding a list to a set (TypeError).
- Insertion feedback: C++
insertreturns a bool; JSaddreturns the set; Pythonaddreturns None — compare sizes in JS/Python. - Order: JS Set is insertion-ordered; Python set and C++ unordered_set are not.
- Set algebra: Python has operators; ES2025 adds methods to JS; C++ needs manual loops for unordered sets.
- Composite members: Python tuples hash structurally; JS and C++ need string/number encoding or a custom hash.
Complexity
| Operation | Average | Worst | Note |
|---|---|---|---|
| Access | — | — | No positional access; membership only. |
| Search | O(1) | O(n) | |
| Insert | O(1) | O(n) | Amortized; resize is O(n). |
| Delete | O(1) | O(n) | |
| Update | — | — | Keys are immutable; remove and re-add. |
| Contains | O(1) | O(n) | |
| Union / Intersection | O(|A| + |B|) | O(|A| · |B|) | |
| Space | O(n) | ||
Advantages & disadvantages
- Constant-time membership with automatic deduplication.
- Less memory than a hash map when values are unneeded.
- Set algebra is built in and readable.
- Unordered: no min/max, no sorted iteration, no range queries.
- Worst case
O(n)under adversarial hashing; resize pauses. - Cannot store duplicates or multiplicities — use a counting Hash Map (multiset) for that.
Use cases
- Visited sets in Breadth-First Search (BFS) / Depth-First Search (DFS) and cycle detection.
- Deduplicating input, computing distinct counts.
- Longest consecutive sequence, contains-duplicate, intersection of arrays.
- Blocklists and allowlists; seen-substring sets in Rolling Hash (Polynomial Hashing) problems.
- Membership tests and deduplication with no need for values.
- Visited tracking in graph and grid searches.
- Set algebra between collections.
- You need counts per element — use a Hash Map counter.
- Elements are small integers with a known range — a boolean Array or bitset (Bit Masks) is faster and denser.
- You need ordered iteration or range queries — use a
TreeSet/ balanced BST. - Approximate membership at huge scale is acceptable — a Bloom Filter uses far less memory.
Alternatives
Common mistakes
- Building a set inside a loop, making the algorithm
O(n²)instead of building once and querying. - Using a mutable object as a set element and then mutating it.
- Using a
listfor membership in Python (x in listisO(n)). - Marking nodes visited on dequeue rather than on enqueue in BFS, which lets duplicates flood the queue.
- Expecting JavaScript
Setto deduplicate structurally equal objects or arrays — it uses reference identity.
Interview patterns
- Contains Duplicate: add each element, return true if
addfails. - Longest Consecutive Sequence: only start counting from
xifx - 1is absent, givingO(n). - Intersection of Two Arrays: set of the smaller array, filter the larger.
- Happy Number / cycle in a function iteration: store seen states.
- Word Ladder: word set for
O(1)neighbour validity, visited set for BFS.
- Recognizing the approach from an array and a targetIntermediate
- Deciding whether O(n²) can be improvedIntermediate
- Choosing between BFS, DFS, Dijkstra and DPAdvanced
- Hash map or array?Beginner
- Two SumBeginner
- Longest Substring Without Repeating CharactersIntermediate
- Course ScheduleIntermediate