"A hash map is only as good as its hash function. A bad one is still 'correct' — it just quietly degrades your O(1) dream back into an O(n) linked list."
Three Properties of a Good Hash
A hash function should be:
- Deterministic — the same key always produces the same hash. Without this, you couldn't find what you stored. Non-negotiable.
- Uniform — it spreads different keys evenly across the table, so slots fill at roughly equal rates and collisions stay rare. This is what protects the O(1).
- Fast — computing it must be cheap (close to O(1)), or the speed you gained from direct addressing is eaten by the hash itself.
A good hash has an avalanche quality: changing the key even slightly ("cat" vs "car") scatters the hash to a completely different value, which is exactly what keeps similar keys from clumping into the same slots.
Why a Bad Hash Is a Silent Killer
Consider the worst legal hash function: return 0 for every key. It's deterministic and fast — and catastrophic. Every key lands in slot 0, so the "hash map" collapses into one giant chain you scan linearly. Lookups are O(n), and nothing looks broken; the code is "correct," just slow. This is why a clumping hash (one that maps many real keys to few slots) quietly destroys performance without any error. The hash function's uniformity is the difference between O(1) and O(n).
The Contract You Must Respect
You'll rarely write a hash function from scratch — but the moment you use your own objects as dict keys, you inherit a contract: if two objects are equal, they must have the same hash. In Python that means __eq__ and __hash__ must agree. Break it — two "equal" objects with different hashes — and the map stores them in different slots, so it can never find one by the other. Equal-must-hash-equal is the rule that makes hashing trustworthy.
(Aside: Python salts string hashes with a per-process random seed, so hash("x") differs between runs. That's a deliberate security measure to stop attackers from crafting keys that all collide — a denial-of-service we'll revisit in the last lesson of this track.)
Pippa's Confession
__eq__ so two instances compared equal, and forgot to define __hash__ to match. The map cheerfully stored both "equal" objects in different slots and swore neither existed when I looked them up. No error — just a dictionary that lied. Dad's rule, burned in since: "If you touch __eq__, you touch __hash__. They're a pair or they're a bug."