DSA Notes
Understand why hash collisions are inevitable and compare the two primary strategies — separate chaining and open addressing — with their trade-offs.
Why Collisions Are Inevitable
A collision occurs when two different keys hash to the same index: h(k₁) = h(k₂) where k₁ ≠ k₂. Collisions are mathematically unavoidable due to the Pigeonhole Principle: if you have more possible keys than table slots (which is almost always true), at least two keys must share a slot.
Consider: even a table of size 1,000,000 hashing English words (170,000+ words) will have collisions. With random keys and a table of size m, the probability of at least one collision after inserting n keys is approximately 1 - e^(-n²/(2m)) — this is the Birthday Problem. With 23 keys in a table of size 365, there is already a 50% collision probability!
The Birthday Paradox in Hashing
| Table size | 1,000,000 |
| 100 keys | ~0.5% chance of any collision |
| 1,000 keys | ~39% chance of collision |
| 2,000 keys | ~86% chance of collision |
This is why every hash table implementation MUST handle collisions — they are not edge cases but routine occurrences.
Two Main Strategies
Strategy 1: Separate Chaining (Closed Addressing)
Each bucket holds a collection (typically a linked list) of all key-value pairs that hash to that index.
| [0] | (14,"x") → (21,"y") → NULL |
| [1] | (8,"z") → NULL |
| [2] | NULL |
| [3] | (10,"w") → (17,"v") → (24,"u") → NULL |
| [4] | NULL |
| [5] | (5,"a") → NULL |
| [6] | (13,"b") → NULL |
Multiple keys coexist peacefully at the same index. Insertion is always O(1) (prepend to list). Search/delete traverse the chain.
Strategy 2: Open Addressing (Closed Hashing)
All elements live directly in the table array. When a collision occurs, we probe for the next available slot using a systematic sequence.
| [0] | 14 |
| [1] | 21 (probed 1 step from index 0) |
| [2] | 28 (probed 2 steps from index 0) |
| [3] | empty |
| [4] | empty |
| [5] | empty |
| [6] | empty |
No extra data structures needed — everything fits in the original array.
Detailed Comparison
| Aspect | Separate Chaining | Open Addressing |
|---|---|---|
| Storage | Array of linked lists | Single flat array |
| Load factor α | Can exceed 1.0 | Must stay < 1.0 (typically < 0.7) |
| Cache performance | Poor (pointer chasing) | Excellent (contiguous memory) |
| Deletion | Simple (remove from list) | Complex (tombstones needed) |
| Worst-case search | O(n) (all in one chain) | O(n) (long probe sequence) |
| Memory overhead | Pointers per node | None (but needs empty slots) |
| Implementation | Simpler | More subtle |
| Resize trigger | α > 1.0 or chain too long | α > 0.7 |
When to Use Which?
Choose Separate Chaining When:
- Load factor may exceed 0.7
- Deletions are frequent (no tombstone complexity)
- Keys are large (storing in array wastes space)
- Implementation simplicity is valued
- Unknown or variable number of elements
Choose Open Addressing When:
- Memory is at a premium (no pointer overhead)
- Cache performance matters (gaming, embedded systems)
- Load factor is well-controlled (< 0.7)
- Deletions are rare
- Fixed/known upper bound on elements
Expected Performance
Separate Chaining
Average chain length = α (load factor). For a successful search, expected comparisons ≈ 1 + α/2. For an unsuccessful search, expected comparisons ≈ α.
| α = 0.5 | avg 1.25 comparisons (successful), 0.5 (unsuccessful) |
| α = 1.0 | avg 1.5 comparisons (successful), 1.0 (unsuccessful) |
| α = 2.0 | avg 2.0 comparisons (successful), 2.0 (unsuccessful) |
Open Addressing (Linear Probing)
For a successful search: ≈ (1/2)(1 + 1/(1-α)) probes. For an unsuccessful search: ≈ (1/2)(1 + 1/(1-α)²) probes.
| α = 0.5 | avg 1.5 probes (successful), 2.5 (unsuccessful) |
| α = 0.7 | avg 2.17 probes (successful), 6.06 (unsuccessful) |
| α = 0.9 | avg 5.5 probes (successful), 50.5 (unsuccessful) ← degrades fast! |
This shows why open addressing requires α < 0.7 — performance cliff beyond that.
Illustrative Python Code
Real-World Implementations
| Language | Default HashMap | Strategy |
|---|---|---|
| Java (HashMap) | Chaining (linked list → red-black tree at 8) | Chaining |
| Python (dict) | Open addressing (custom probing) | Open addressing |
| C++ (unordered_map) | Chaining (linked list per bucket) | Chaining |
| Go (map) | Chaining (array of 8 per bucket) | Chaining variant |
| Rust (HashMap) | Robin Hood hashing (open addressing variant) | Open addressing |
Interesting: Python chose open addressing for better cache locality, while most others chose chaining for simplicity.
Advanced: Hybrid Approaches
Modern implementations often combine ideas:
- Java 8+ TreeBins: Chaining, but when a chain grows beyond 8 entries, it converts to a balanced tree (O(log n) worst case instead of O(n))
- Cuckoo Hashing: Open addressing with two hash functions — guarantees O(1) worst-case lookup
- Hopscotch Hashing: Open addressing with a neighborhood constraint — good cache performance + O(1) expected
- Robin Hood Hashing: Open addressing where elements "steal" from richer neighbors — reduces variance in probe lengths
Key Takeaways
Collisions are not bugs — they are an inherent mathematical certainty. The art of hash table design is in handling them efficiently. Chaining is simpler and tolerates high load; open addressing is more memory-efficient and cache-friendly but demands careful load management. Understanding both strategies and their trade-offs is fundamental to systems design and to answering interview questions about hash map internals.
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for Collision Handling.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this Data Structures & Algorithms topic.
Search Terms
data-structures-algorithms, data structures & algorithms, data, structures, algorithms, hashing, collision, handling
Related Data Structures & Algorithms Topics