Hash tables
A hash function turns a key into an array index, so lookup is one computation — until two keys collide.
A BST searches in logarithmic time by keeping everything ordered. A hash table throws ordering away and goes straight to the answer: run the key through a hash function to get an array index, then look there. That is on average and needs no comparisons — as long as two different keys do not land on the same index.
A coat check, not a library
A library finds a book by category and shelf order. A coat check takes your ticket number, computes where to look, and hands you the coat immediately — no searching at all. The table is only as good as its spreading: if everyone receives the same number, the clerk must rummage.
Insert keys below, shrink the table, and watch collisions appear. Toggle between chaining and linear probing to see two ways of handling them.
Insert keys, shrink the table, and watch collisions appear
bucket 0
bucket 1
bucket 2
bucket 3
bucket 4
bucket 5
bucket 6
Good hashing scatters keys uniformly; collisions are then rare, so the average cost of insert and lookup stays close to O(1). Push the load factor past roughly 0.7 and both chaining and probing degrade — which is why real hash tables resize (rehash) when they fill.
Hash, then index
A hash function maps an arbitrary key to an integer; the index is that integer modulo the table size. A good function is deterministic (same key, same index), uniform (spreads keys evenly), and fast. For strings, a polynomial rolling hash is standard:
The base and modulus must be chosen to avoid patterns; the same construction powers the Rabin-Karp string matcher in a later lesson.
Collisions are inevitable
By the pigeonhole principle, distinct keys must sometimes share an index. Two families of fixes:
- Chaining stores a list per bucket and appends colliding keys. A bucket of length costs to search, so the average is where is the load factor (keys ÷ buckets).
- Open addressing keeps everything in the array and probes forward (linear probing) until it finds a free slot. Deletion needs tombstones, or it breaks the probe chain.
Chaining is simpler; open addressing has better cache behaviour in languages where the array is compact.
The load factor is the control knob
As grows, collisions multiply and the constant in "average " grows with them. Real hash tables resize — allocate a larger array and re-insert every key — once crosses a threshold (Python's dict grows around two-thirds full, and resizes under a memory cap too). Rehashing is , but spread over the insertions that caused it the amortised cost is still .
Hashing assumes keys do not change
A key's index is computed from its value, so a mutable key whose hash changes after insertion becomes unfindable. This is why dict keys must be immutable and hashable: lists and sets cannot be keys, but tuples (of hashables) and strings can.
Worst case, a hostile input can force every key into one bucket and degrade the table to per operation. This is why production hash functions are randomised per process — an attacker cannot predict the buckets.
Illustrative vs real
The explorer uses a tiny table and simple strings so collisions happen often and are visible; real tables are sized in powers of two or primes and hold thousands of entries. It uses a textbook polynomial hash rather than Python's SipHash. The behaviour — average , collision handling, resizing — is the same; only the numbers differ.
Check yourself
Eduspheria wiki · Programming & Data Structures, Data structures
0 / 5 answered
From the assignment paper
Modeled on NITJ AI-507, Assignment/Quiz
0 / 5 answered
Where next: graphs — the most general structure, where any node may connect to any other.