Intuition The one-line idea
A hash table maps keys to an array index. But two different keys can land on the same index (a collision ). Chaining says: "Fine — at each index, keep a linked list of all keys that hashed there." Lookups walk that small list. As long as the lists stay short, everything is fast.
Definition The collision problem
A hash function h h h maps a universe of U U U possible keys into m m m slots: h : U → { 0 , 1 , … , m − 1 } h: U \to \{0,1,\dots,m-1\} h : U → { 0 , 1 , … , m − 1 } . Since ∣ U ∣ ≫ m |U| \gg m ∣ U ∣ ≫ m almost always, by the pigeonhole principle at least two keys must share a slot. We cannot avoid collisions; we can only handle them.
WHY chaining specifically? Two key strategies exist:
Open addressing — store everything inside the array, probe for the next free slot.
Chaining — store a separate container (a linked list) per slot.
Chaining is chosen when we want: simple deletion, graceful behavior when the table gets crowded, and no clustering headaches.
Definition Separate chaining
The table is an array T[0..m-1]. Each T[i] is the head pointer of a linked list (a "chain" / "bucket") holding every stored key k k k with h ( k ) = i h(k)=i h ( k ) = i .
HOW the operations work (let n n n = number of stored keys):
Op
What it does
Cost
insert(k)
compute h ( k ) h(k) h ( k ) , prepend to list T [ h ( k ) ] T[h(k)] T [ h ( k )]
O ( 1 ) O(1) O ( 1 )
search(k)
compute h ( k ) h(k) h ( k ) , scan list T [ h ( k ) ] T[h(k)] T [ h ( k )]
O ( list length ) O(\text{list length}) O ( list length )
delete(k)
search, then unlink the node
O ( list length ) O(\text{list length}) O ( list length )
Insert is O ( 1 ) O(1) O ( 1 ) because we prepend at the head (no need to check duplicates if we allow them, or O ( len ) O(\text{len}) O ( len ) if we must reject duplicates).
α = n m \alpha = \frac{n}{m} α = m n
where n n n = number of keys stored, m m m = number of buckets. It is the average number of keys per bucket .
Intuition What this means in plain words
The "1 1 1 " is "always compute the hash & touch one bucket." The "α \alpha α " is "then walk through, on average, α \alpha α items." If you keep α \alpha α bounded by a constant (say ≤ 0.75 \le 0.75 ≤ 0.75 ), every operation is expected O ( 1 ) O(1) O ( 1 ) . That is the whole game.
Worked example Reading off costs
n = 100 , m = 100 ⇒ α = 1 n=100, m=100 \Rightarrow \alpha=1 n = 100 , m = 100 ⇒ α = 1 : expected ~1 comparison. Great.
n = 1000 , m = 100 ⇒ α = 10 n=1000, m=100 \Rightarrow \alpha=10 n = 1000 , m = 100 ⇒ α = 10 : expected ~10 comparisons. Slow.
n = 100 , m = 1000 ⇒ α = 0.1 n=100, m=1000 \Rightarrow \alpha=0.1 n = 100 , m = 1000 ⇒ α = 0.1 : ~0.1 comparisons but wasting memory.
Why this step? It shows α \alpha α is a tunable knob trading speed (small α \alpha α ) against memory (large α \alpha α ).
If we never grow m m m , then as we insert keys n n n rises forever and α = n / m \alpha = n/m α = n / m blows up → chains get long → O ( 1 ) O(1) O ( 1 ) degrades to O ( n ) O(n) O ( n ) . To keep α \alpha α small we must grow m m m .
Pick a threshold (e.g. α m a x = 0.75 \alpha_{max}=0.75 α ma x = 0.75 ). When insert would push α \alpha α above it:
Allocate a new array of size m ′ = 2 m m' = 2m m ′ = 2 m .
Rehash : recompute h ′ ( k ) = ( hash ( k ) m o d m ′ ) h'(k) = (\text{hash}(k) \bmod m') h ′ ( k ) = ( hash ( k ) mod m ′ ) for every key and re-insert it.
Discard the old array.
WHY double, not add a constant? Doubling makes resizes exponentially rare .
Worked example A resize in action
Table m = 4 m=4 m = 4 , α m a x = 0.75 ⇒ \alpha_{max}=0.75 \Rightarrow α ma x = 0.75 ⇒ resize when n > 3 n>3 n > 3 . Keys: 5,9,13,1 with h ( k ) = k m o d 4 h(k)=k\bmod 4 h ( k ) = k mod 4 .
insert 5 → slot 1; 9 → slot 1 (chain: 9→5); 13 → slot 1 (chain 13→9→5); n = 3 n=3 n = 3 , α = 0.75 \alpha=0.75 α = 0.75 , OK.
insert 1 → would make n = 4 n=4 n = 4 , α = 1 > 0.75 \alpha=1>0.75 α = 1 > 0.75 . Resize to m = 8 m=8 m = 8 . Now h ( k ) = k m o d 8 h(k)=k\bmod 8 h ( k ) = k mod 8 :
5→5, 9→1, 13→5 (chain 13→5), 1→1 (chain 1→9).
Why this step? Notice the long chain at slot 1 got split across slots 1 and 5 — that's exactly how resizing flattens the chains.
Common mistake "Worst case is
O ( 1 ) O(1) O ( 1 ) because it's a hash table."
Why it feels right: marketing says "hash tables are O ( 1 ) O(1) O ( 1 ) ." Reality: that's average/expected under uniform hashing. If an adversary (or a bad hash) sends every key to one bucket, all n n n land in one chain → worst case O ( n ) O(n) O ( n ) per operation. Fix: say "expected O ( 1 + α ) O(1+\alpha) O ( 1 + α ) , worst case O ( n ) O(n) O ( n ) ." Use a good/randomized hash to make the bad case unlikely.
Common mistake "Just make
m m m huge so chains are always empty."
Why it feels right: big m m m → tiny α \alpha α → fast. Reality: you waste O ( m ) O(m) O ( m ) memory and lose cache locality; mostly-empty arrays thrash memory. Fix: keep α \alpha α near a constant like 0.75 0.75 0.75 via resizing, not by over-allocating once.
Common mistake "Resizing is cheap, so do it every insert by adding 10 slots."
Why it feels right: keeps α \alpha α perfectly tuned. Reality: rehashing is O ( m ) O(m) O ( m ) ; growing by a constant makes total work Θ ( n 2 ) \Theta(n^2) Θ ( n 2 ) . Fix: grow multiplicatively (double), giving O ( 1 ) O(1) O ( 1 ) amortized.
Common mistake "Load factor of chaining must be
≤ 1 \le 1 ≤ 1 ."
Why it feels right: open addressing dies at α = 1 \alpha=1 α = 1 (table full). Reality: chaining works fine at α > 1 \alpha>1 α > 1 — buckets just hold multiple items; it slows gracefully. Fix: the α ≤ 1 \alpha\le1 α ≤ 1 rule is for open addressing , not chaining.
Recall Predict before revealing (Forecast-then-Verify)
Define load factor and state expected search cost. → α = n / m \alpha=n/m α = n / m ; expected O ( 1 + α ) O(1+\alpha) O ( 1 + α ) .
Why double m m m instead of adding a constant? → Geometric series gives O ( 1 ) O(1) O ( 1 ) amortized vs O ( n ) O(n) O ( n ) .
What's the worst case for a chained lookup and when? → O ( n ) O(n) O ( n ) , when all keys collide into one chain.
Does chaining break at α = 1 \alpha=1 α = 1 ? → No, only open addressing does; chaining degrades gracefully.
Recall Feynman: explain to a 12-year-old
Imagine a wall of mailboxes numbered 0–9. A rule (the hash) tells each letter which box it belongs to. Sometimes two letters go to the same box — so inside each box you keep a little string of letters clipped together (the linked list). To find a letter, go to its box and flip through that small string. If boxes get too crowded (too many letters per box on average — that's the load factor ), you buy a bigger wall with twice as many boxes and re-sort all the letters so each box is light again. Light boxes = fast finding.
Mnemonic Remember the knobs
"CALR" — C ollisions → A lpha = n/m → L ist length sets cost (1 + α 1+\alpha 1 + α ) → R esize (double) to reset alpha. "Chains Always Like Resizing."
Hash Functions — quality of h h h decides whether uniform-hashing assumption holds.
Open Addressing — the alternative (linear/quadratic probing, dies at α = 1 \alpha=1 α = 1 ).
Amortized Analysis — justifies O ( 1 ) O(1) O ( 1 ) inserts despite occasional O ( m ) O(m) O ( m ) resizes.
Dynamic Arrays — same doubling trick for amortized O ( 1 ) O(1) O ( 1 ) append.
Pigeonhole Principle — why collisions are unavoidable.
Linked Lists — the bucket data structure itself.
What is the load factor of a hash table? α = n / m \alpha = n/m α = n / m , the average number of keys per bucket (
n n n keys,
m m m buckets).
Expected cost of search in a chained hash table? O ( 1 + α ) O(1+\alpha) O ( 1 + α ) under simple uniform hashing.
Worst-case cost of search in a chained hash table and when does it happen? O ( n ) O(n) O ( n ) , when every key hashes to the same bucket (one long chain).
Why double the table size on resize instead of adding a fixed number of slots? Doubling makes total rehash work a geometric series
≤ 2 n \le 2n ≤ 2 n , giving
O ( 1 ) O(1) O ( 1 ) amortized insert; constant growth gives
Θ ( n 2 ) \Theta(n^2) Θ ( n 2 ) total →
Θ ( n ) \Theta(n) Θ ( n ) per insert.
What does rehashing involve? Allocating a larger array (e.g.
2 m 2m 2 m ) and recomputing
h ( k ) m o d m ′ h(k)\bmod m' h ( k ) mod m ′ for every key, re-inserting all of them.
Does chaining stop working at α = 1 \alpha=1 α = 1 ? No — only open addressing fills up at
α = 1 \alpha=1 α = 1 ; chaining keeps working and just slows down linearly in
α \alpha α .
Why is insertion (without duplicate check) O ( 1 ) O(1) O ( 1 ) in chaining? You compute the hash and prepend the node at the head of the bucket's list.
What assumption gives expected O ( 1 + α ) O(1+\alpha) O ( 1 + α ) ? Simple Uniform Hashing: each key is equally and independently likely to hash to any of the
m m m slots.
Typical resize trigger threshold? When
α \alpha α exceeds a chosen max like
0.75 0.75 0.75 .
Expected length of any single bucket? n ⋅ 1 m = α n \cdot \frac{1}{m} = \alpha n ⋅ m 1 = α .
Hash function h maps keys to slots
Load factor a equals n over m
Expected cost O of 1 plus a
Intuition Hinglish mein samjho
Dekho, hash table ek array hota hai jisme ek hash function key ko ek index pe map karta hai. Problem ye hai ki do alag keys kabhi-kabhi same index pe aa jaati hain — isko collision kehte hain, aur ye unavoidable hai (pigeonhole principle). Chaining ka simple jugaad: har bucket (index) pe ek linked list rakho. Jo bhi keys us index pe hash hoti hain, sab us list me clip ho jaati hain. Search karte time bas us chhoti si list ko scan karo.
Sabse important number hai load factor α = n / m \alpha = n/m α = n / m — yani average kitni keys per bucket. Expected search cost nikalti hai O ( 1 + α ) O(1+\alpha) O ( 1 + α ) : "1" matlab hash compute karna aur ek bucket touch karna, "α \alpha α " matlab average itne items walk karna. Agar α \alpha α ko chhota constant (jaise 0.75) ke neeche rakho, to har operation expected O ( 1 ) O(1) O ( 1 ) rehta hai — yahi pura magic hai. Worst case me agar saari keys ek hi bucket me chali jaayein to O ( n ) O(n) O ( n ) ho jaata hai, isliye accha hash function zaroori hai.
Jaise-jaise keys badhti hain, n n n badhta hai aur α \alpha α bada hone lagta hai — chains lambi ho jaati hain. Isliye resizing (rehashing) karte hain: jab α \alpha α threshold cross kare, array ka size double kar do aur saari keys naye m m m ke hisaab se dobara hash karke daal do. Double kyun? Kyunki doubling se total copy-work geometric series ban jaata hai (1 + 2 + 4 + ⋯ < 2 n 1+2+4+\dots < 2n 1 + 2 + 4 + ⋯ < 2 n ), isliye per-insert cost amortized O ( 1 ) O(1) O ( 1 ) rehta hai. Agar tum constant (jaise +10) add karte to total kaam Θ ( n 2 ) \Theta(n^2) Θ ( n 2 ) ho jaata — disaster. Bas yaad rakho: collisions → alpha → chain length → resize .