Rehashing
A hash table is fast only while it is mostly empty. The load factor, keys over slots, says how full it is, and past a limit probe chains get long. So the table doubles and every key is placed again, because key mod capacity changes with the capacity. That costs a full pass, but doubling makes it rare enough that inserts stay O(1) on average.
An empty table of 5 slots. Keys land at key mod 5 and probe forward on a collision. When the load factor, keys over slots, passes 0.70 the table doubles.
Check your understanding
The player pauses before each decision in this run and asks what happens next. Here are all 5, with their answers.
After 12 the table holds 1 of 5 slots. Rehash?
Answer: Under the limit, keep going. 1 / 5 = 0.20 is still within the limit.
After 7 the table holds 2 of 5 slots. Rehash?
Answer: Under the limit, keep going. 2 / 5 = 0.40 is still within the limit.
After 25 the table holds 3 of 5 slots. Rehash?
Answer: Under the limit, keep going. 3 / 5 = 0.60 is still within the limit.
After 3 the table holds 4 of 5 slots. Rehash?
Answer: Over the limit, rehash. 4 / 5 = 0.80 is past 0.70. Probe chains would only get longer from here.
After 18 the table holds 5 of 10 slots. Rehash?
Answer: Under the limit, keep going. 5 / 10 = 0.50 is still within the limit.
How it runs, step by step
An empty table of 5 slots. Keys land at key mod 5 and probe forward on a collision. When the load factor, keys over slots, passes 0.70 the table doubles.
Empty hash table with 5 slots and a load limit of 0.70. Inserting 5 keys: 12, 7, 25, 3, 18.
insert 12. 12 mod 5 = 2 and slot 2 is free. Load 1 / 5 = 0.20, under 0.70.
Insert 12 at slot 2. The load factor is 0.20, under the limit.
insert 7. 7 mod 5 = 2, taken by 12, so probe 1 slot forward to slot 3. Load 2 / 5 = 0.40, under 0.70.
Insert 7 at slot 3 after 1 collision probe. The load factor is 0.40, under the limit.
insert 25. 25 mod 5 = 0 and slot 0 is free. Load 3 / 5 = 0.60, under 0.70.
Insert 25 at slot 0. The load factor is 0.60, under the limit.
insert 3. 3 mod 5 = 3, taken by 7, so probe 1 slot forward to slot 4. Load 4 / 5 = 0.80, over 0.70. Time to rehash.
Insert 3 at slot 4 after 1 collision probe. The load factor is 0.80, over the limit.
Double the table from 5 to 10 slots. Every key must be placed again, because key mod 10 is not key mod 5. 4 keys to move: 25, 12, 7, 3.
The table grows from 5 to 10 slots. All 4 keys are re-inserted.
Move 25. 25 mod 10 = 5 and slot 5 is free.
25 is re-inserted at slot 5.
Move 12. 12 mod 10 = 2 and slot 2 is free.
12 is re-inserted at slot 2.
Move 7. 7 mod 10 = 7 and slot 7 is free.
7 is re-inserted at slot 7.
Move 3. 3 mod 10 = 3 and slot 3 is free.
3 is re-inserted at slot 3.
insert 18. 18 mod 10 = 8 and slot 8 is free. Load 5 / 10 = 0.50, under 0.70.
Insert 18 at slot 8. The load factor is 0.50, under the limit.
5 keys in 10 slots, load 5 / 10 = 0.50. 1 rehash moved 4 keys in total. Doubling keeps that under one extra move per insert on average, so insert stays O(1) amortized.
Done. 5 keys in 10 slots with a load factor of 0.50. 1 rehash moved 4 keys in total.
Remember
- Load factor is keys divided by slots. Open addressing keeps it under about 0.7, chaining tolerates more.
- Rehashing moves every key, because key mod capacity is a different slot once the capacity changes.
- Doubling means each key moves O(1) times on average, so insert is O(1) amortized despite the occasional full pass.
Topics covered
Related
Where this is used
DatabasesRedis dictionaries
Redis keeps two hash tables per dict and rehashes incrementally: a step moves one bucket from the old table to the new one, and while the move is in progress every lookup checks both tables. A single-threaded server that stopped to move a million keys at once would freeze every connected client, so the pass is deliberately smeared over thousands of commands.
Language runtimesjava.util.HashMap resize
HashMap keeps its capacity a power of two and resizes at a load factor of 0.75. Because the capacity exactly doubles, a key's new index is either its old index or that index plus the old capacity, decided by the single new bit of the stored hash. Resize therefore splits each bucket's list in two by testing one bit, without recomputing any hash codes.
Distributed systemsConsistent hashing in sharded caches
Sharding by key mod N is a hash table whose slots are servers, so adding one machine is a rehash that relocates nearly every key, and a cache that loses nearly every entry at once dumps its whole load onto the database. Amazon Dynamo and Apache Cassandra place nodes on a hash ring instead, so joining a node moves only the keys in its arc, about 1/N of them, and the rest of the cluster is untouched.
InterpretersCPython dict growth
A Python dict grows once it is two thirds full, and it sizes the replacement from the number of live entries rather than the number of slots ever used, so a dict that has had many deletions can be rebuilt smaller than it was. The rebuild also compacts the dense entry array behind the index table, which is what keeps iteration proportional to the keys present instead of to the capacity.
Why it works this way
Why double the capacity instead of adding a fixed block of slots?
Growing by a constant c means a rehash every c inserts, and each pass touches every key already stored, so n inserts do roughly n squared over 2c work and a single insert costs O(n) on average. Doubling makes the passes exponentially rarer as the table grows: their sizes are n, n/2, n/4 and so on, under 2n moves in total. That total divided over n inserts is where the amortized O(1) comes from.
Why 0.7, and why chaining can run past 1.0
With linear probing an unsuccessful lookup costs about (1 + 1/(1-a)^2)/2 probes for load factor a: roughly 6 probes at 0.7 but roughly 50 at 0.9. The cost is not proportional to how full the table is, it blows up as the last slots disappear, so open addressing has to resize well before the table fills. Chaining degrades gently, since the average chain length is just a, which is why a chained table can sit at 1.0 or higher and only pay a slightly longer walk.
Deleting a key does not free its slot
Under open addressing you cannot blank a slot on delete, because a probe that walked past that slot to place a later key would now stop at the gap and report the key missing. The usual fix is a tombstone, a marker meaning keep probing, but tombstones lengthen probes while counting as neither live keys nor free space, so a table churned by inserts and deletes gets slow at a load factor that still looks small. The cure is a rehash into a table of the same capacity, which is the reminder that not every rehash is a growth.
Amortized O(1) is not O(1) on the insert that triggers it
One insert in every n pays for the whole pass, so a table holding a million keys has one insert that moves a million keys. That shows up as a latency spike, and averaging it away is exactly what a request budget, a game frame or an audio callback cannot do. Code with a deadline either sizes the table up front, with HashMap.newHashMap(n) or reserve or make(map[K]V, n), or spreads the move across many operations instead of doing it in one. Watch the Java form: the HashMap(n) constructor takes a bucket count, not a key count, so passing the number of keys you expect still resizes once you pass 0.75 of it, which is why newHashMap was added.
Read more
- Hash tableWikipedia
- Open addressingWikipedia
- Consistent hashingWikipedia
- HashMap: load factor and resizingOracle Java 21 · docs.oracle.com
- Incremental rehashing in dict.cRedis · github.com