№ 51 · computing

Why a hash table finds things in constant time

A hash table finds a key among a million others about as fast as among ten. It does not search. It computes where the key must be, and looks there.

Address instead of search

Each item has a key — a word, a number, a file name — and you later ask "is this key here?" Keep the items in an array of m slots, called buckets. A hash function turns any key into a bucket number from 0 to m−1. Storing computes the bucket and puts the item there; finding computes the same bucket and looks. The arithmetic costs the same for ten items or ten million.

Why it matters

This is the dictionary built into most programming languages; the MIT lecture notes list where else it sits: compilers mapping names to variables, routers mapping addresses to wires, virtual memory mapping virtual to physical addresses. The notes' comparison point, a balanced search tree, costs time logarithmic in the item count. Hashing removes that dependence — on average. The rest of this page is what that hides.

Interactive Press insert 8 keys a few times and watch the chains grow and the measured lookup cost rise and fall with α; flip first-letter hash to see the same keys pile into a few buckets; press resize ×2 to double the buckets and rehash.

A real table, not a picture of one. Keys are English words drawn without repetition from a fixed list of 200; the good hash is a multiply-and-shift hash of the whole word (a fixed odd multiplier — the golden-ratio constant rather than a random draw — in the form given in the lecture notes), the bad one uses only the first letter. Each bucket's chain is drawn as stacked blocks. Measured is the exact average number of list nodes examined when every stored key is looked up once — a successful search stops at the key. It is compared with 1 + α, the expression whose order Θ(1 + α) the notes give for expected cost under simple uniform hashing (1 for the hash, α for walking a whole list). The measured line sits below it, because a successful search stops at its key partway down the chain instead of walking to the end; the notes give the order of growth, not the constant, so what to look for is that both move together with α. Resize ×2 doubles m and reinserts every key, so α halves and the chains shorten; the table also does this by itself when n reaches m, the doubling rule the notes settle on. All numbers on this panel are computed live from the table you see.

Collisions are inevitable

Far more keys are possible than buckets, so two must sometimes share one: a collision, and it comes sooner than intuition expects. Drop random keys into 64 buckets and the first collision arrives, on average, with the 11th key (10.7, computed exactly). With 365 buckets — the birthday problem — about the 25th (24.6). With 1,024, about the 41st. Collision-free is not realistic.

The notes' fix is chaining: each bucket holds a linked list of whatever hashed to it; a lookup walks that one list. Cost: one hash plus the chain length.

The load factor

How long is a chain? The lecture assumes — openly calling it cheating — simple uniform hashing: each key equally likely to land in any bucket, independently of the others. Then n keys in m buckets give n/m expected per bucket. The notes name this ratio the load factor α and give the expected search cost as Θ(1 + α): 1 for hashing, α for walking the list. Constant whenever α is — whenever m keeps pace with n.

Lecture 9 works out the rule the notes settle on (growing one bucket at a time is rejected first): when n reaches m, allocate 2m buckets and reinsert every key, since the hash function itself changes with m. That rebuild costs time proportional to n, but happens only at insertion 1, 2, 4, 8, … so the total over n insertions is still proportional to n. The notes call the cost per insertion Θ(1) amortized — averaged over the whole sequence, not promised per operation.

When it goes wrong

All of this rests on the keys spreading out. The notes state the worst case plainly: if all n keys hash to one slot, every operation costs Θ(n) — the table is one long list. A hash function reading only part of the key does this by itself; try the first-letter hash in the interactive. The notes' answer is universal hashing: pick the hash function at random from a family when building the table. Any two distinct keys collide under the chosen function with chance 1/m, so the expected number of other keys in your bucket is again α, assuming nothing about the keys. The worst case does not disappear; the random choice makes it unlikely, not impossible.

In short

Compute the bucket, look inside. Collisions arrive early, so each bucket keeps a chain of expected length n/m, the load factor. Doubling keeps that ratio bounded; a random hash function keeps chains short on any input not built to defeat it.

Where this comes from

  1. 6.006 Introduction to Algorithms, Fall 2011, Lecture 8: Hashing I linked only, not reproduced
    MIT OpenCourseWare · 2011
    ocw.mit.edu/courses/6-006-introduction-to-algorithms-fall-2011/d3e4d64266d481c74c9e7c15e09999fe_MIT6_006F11_lec08.pdf
  2. 6.006 Introduction to Algorithms, Fall 2011, Lecture 9: Hashing II linked only, not reproduced
    MIT OpenCourseWare · 2011
    ocw.mit.edu/courses/6-006-introduction-to-algorithms-fall-2011/160b3b5f9da2e03815ca1e6ee0dba62a_MIT6_006F11_lec09.pdf