"O(1) lookup isn't free forever — it's a promise the hash map keeps by quietly growing itself before it gets too crowded. Stop the growing and the promise breaks."
The Load Factor
The load factor is the ratio of stored entries to available slots: load = entries / slots. It's the single number that predicts a hash map's health. At a low load (say 0.3), keys are spread thin, collisions are rare, and lookups are crisply O(1). As the load climbs toward 1, slots fill up, collisions pile on, chains lengthen (or probe sequences stretch), and your O(1) quietly rots toward O(n). The load factor is the gauge on the dashboard — watch it and you know whether the map is healthy.
The Fix: Grow and Rehash
When the load factor crosses a threshold, the map resizes: it allocates a bigger table (typically double) and rehashes every existing entry into it. Why rehash, not just copy? Because the slot index is hash % size, and size just changed — every key needs its index recomputed for the new table. Rehashing is an O(n) operation. But — and you've seen this exact movie — because the table doubles, resizes happen exponentially less often, so the O(n) cost spread across all the insertions averages out to O(1) amortized per insert. It's the dynamic-array doubling story from the Complexity track, playing out one layer up.
Where the Threshold Sits
The right threshold depends on the collision strategy. Chaining tolerates a load above 1 (the chains just get a bit longer), so it can run fuller. Open addressing degrades sharply as it fills — probe sequences explode near full — so it must resize earlier; CPython's dict keeps the load under roughly two-thirds. That two-thirds rule is precisely why a Python dict occasionally pauses to resize as you add keys, and why it uses noticeably more memory than the raw number of entries would suggest. The empty slots aren't waste — they're the breathing room that keeps lookups O(1).