"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 — hashing should be cheap relative to the lookup. Fixed-size integer keys can be treated as O(1), but first hashing a long string or tuple generally reads the key and costs O(key length).
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
Hashable objects must obey two rules: equal values have equal hashes, and the hash must not change while the object is stored. In Python, overriding value-based __eq__ without an appropriate __hash__ normally sets __hash__ = None, making instances unhashable instead of silently corrupting a dict. Strings, integers, and tuples containing only hashable values are common keys; mutable lists, dictionaries, and sets are unhashable by default. Mutable equality state should not drive a key's hash.
(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__ to a custom object and tried to use it as a dict key. Python slammed the door with "unhashable." What felt like an obstacle was a guardrail against a dictionary that lies. Dad's rule burned in: "Design equality and hashing as a pair—and once a key is stored, don't mutate the state its hash depends on."