Sonith Manakkat

Algorithms, part 4 of 129 min read11 visuals

Hashing and hash tables

How a hash function turns a key into a bucket number so lookups take about one step on average, what collisions are, how tables grow, and why password hashing is a different job.

  • A hash function turns a key into a bucket so lookup is about one step on average
  • Collisions are normal and are handled by chaining or open addressing
  • Hash tables for data and password hashing are different jobs

Share on LinkedIn

Share
Reading level

A hash table lets you find something by name in about one step, however many things are stored. That sounds like magic, and the trick behind it is surprisingly simple. This part uses a coat-check picture, an animation with a collision, and an Expert view on the details that make hash tables work and sometimes fail.

The coat-check picture

At a coat check, you hand over a coat and get a numbered ticket. Later the ticket tells staff exactly which hook to go to, with no searching along the rail.

Key (thename)HashfunctionBucketnumberItem found
A hash table works like a coat check.
Expert viewDefinitions

A hash table stores key-value pairs in an array of buckets. A hash function maps a key to an integer; the table reduces that integer to a valid bucket index, often by taking the remainder after dividing by the number of buckets. A good function is deterministic (the same key always gives the same number) and spreads keys evenly. Lookup, insert and delete all start by hashing the key.

Watch it work

01234A hash table has numbered buckets. A hash functionturns each key into a bucket number.012ann34"ann" maps to bucket 2.012annbob34"bob" also maps to bucket 2: a collision, so it ischained after "ann".012annbobcat34"cat" also maps to bucket 2: a collision, so it ischained after "ann".012annbobcatdan34"dan" also maps to bucket 2: a collision, so it ischained after "ann".0eve12annbobcatdan34"eve" maps to bucket 0.0evefay12annbobcatdan34"fay" also maps to bucket 0: a collision, so it ischained after "eve".0evefay12annbobcatdan34To look a key up, hash it again and check only thatone bucket.
Words are hashed to buckets. Watch what happens when two words land in the same bucket.
Show the 8 steps as text
  1. A hash table has numbered buckets. A hash function turns each key into a bucket number.
  2. "ann" maps to bucket 2.
  3. "bob" also maps to bucket 2: a collision, so it is chained after "ann".
  4. "cat" also maps to bucket 2: a collision, so it is chained after "ann".
  5. "dan" also maps to bucket 2: a collision, so it is chained after "ann".
  6. "eve" maps to bucket 0.
  7. "fay" also maps to bucket 0: a collision, so it is chained after "eve".
  8. To look a key up, hash it again and check only that one bucket.
Expert viewWhy collisions are unavoidable

There are far more possible keys than buckets, so by the pigeonhole principle some keys must share a bucket. Even with few keys, collisions appear sooner than intuition suggests: the birthday problem shows that among only 23 people, two share a birthday with probability a little over one half. So a design must assume collisions and handle them efficiently.

  1. 1Take the keyFor example the word "cat"
  2. 2Hash itThe hash function gives a number
  3. 3Pick the bucketReduce the number to a bucket index
  4. 4Compare keysCheck the items in that bucket against the key
  5. 5Found or notA match returns the value; no match means the key is absent

Takeaway. Hashing jumps straight to one bucket, and only the few items there are compared.

A lookup in a hash table, step by step.

Handling collisions

Chaining

  • Each bucket holds a small list of items
  • A collision just adds to the list

Open addressing

  • Items live in the array itself
  • On a collision, try the next free slot by a fixed rule
Two common plans.
Expert viewChaining and open addressing compared

With chaining, each bucket stores a collection (often a linked list), and a lookup compares keys along that chain. With open addressing, every item sits directly in the array; a collision triggers probing, such as stepping to the next slot (linear probing) or following another deterministic sequence. Deleting under open addressing needs care, because simply emptying a slot can break the probe path of other items, so a marker is usually left behind. Open addressing keeps data compact, but its performance degrades sharply as the table fills.

Load factor and resizing

  1. Count the itemsCompare with the number of buckets
  2. Check the load factorItems divided by buckets
  3. Too full? Make more bucketsOften double the size
  4. Re-hash every itemEach one gets a new bucket in the bigger table
How a table stays fast as it fills.
Expert viewLoad factor and amortised cost

The load factor is the number of stored items divided by the number of buckets. Tables keep it below a chosen limit by growing when it is exceeded. Growing means creating a larger array and re-inserting every item, which is O(n) for that one insert. If the size grows by a constant factor such as doubling, the cost spread over all the inserts is constant per insert, so inserts are amortised O(1). Where a table must never pause, some designs spread the re-hashing over many operations.

What it costs

OperationAverage caseWorst case
Look up a keyO(1)O(n)
InsertO(1) amortisedO(n)
DeleteO(1)O(n)
Extra spaceO(n)O(n)
  • about 1bucket checked on average
  • up to 1,000,000items checked in the worst case
Looking up one word among a million.
Expert viewAverage versus worst case

The O(1) average assumes a good hash function and a bounded load factor, so each bucket holds only a few items. The worst case is O(n): if every key lands in one bucket, a lookup is a linear scan of everything. (Some designs reduce the chain cost, for example by storing long chains in a balanced structure, but that is an implementation choice and not part of the basic idea.) Hash tables also do not keep keys in sorted order, so "give me the smallest key" or "all keys from A to F" is not what they are for.

Where hashing is used

  • Dictionaries and mapsLook up a value by key: names to phone numbers
  • Sets"Have I seen this before?" in about one step
  • CachesStore recent results by key so repeats are instant
  • Spotting duplicatesHash each record, then compare only those that share a bucket
  • Counting wordsOne counter per word, found by hashing the word
Everyday uses.

Hashing also appears in checksums: a fast hash of a file or message is recomputed later to check that nothing was corrupted by accident.

A different job: passwords

UserServersign upadd saltslow hashstore bothlog inhash againyes or no1234567
  1. User to Server The user chooses a password and sends it over a protected connection
  2. Server (to itself) The server adds a unique random salt for this user
  3. Server (to itself) It runs a deliberately slow password-hashing function
  4. Server (to itself) It stores the salt and the hash, and forgets the password
  5. User to Server Later the user types the password again
  6. Server (to itself) The server hashes the typed password with the stored salt
  7. Server to User (reply) If the two hashes match, access is allowed

Takeaway. The server checks a password by comparing hashes, so it never needs to read the password back.

Storing and checking a password: the server keeps a salt and a slow hash, never the password.

Hash table hash

  • As fast as possible
  • Spreads keys evenly across buckets

Password hash

  • Deliberately slow, so guessing is costly
  • Salted per user so equal passwords look different
Same word, different goals.
Expert viewGood hash functions, and which kind to use

For tables, a good function is deterministic, fast, and uniform: each bucket should be about equally likely, and small changes in a key should scatter the results. It must also agree with equality: keys that are equal must give equal hashes.

Non-cryptographic hashes are built for speed and spread, and suit tables and checksums. Cryptographic hashes add security goals, such as being infeasible to reverse or to find two inputs with the same output. Password storage needs more again: a hash that is slow and expensive to compute, designed to resist guessing at scale, plus a salt to defeat precomputed lists. A fast cryptographic hash is still the wrong tool for passwords.

Expert viewHash-flooding attacks

If an attacker knows the hash function and can choose the keys (for example, names in a web request), they can pick many keys that all collide, forcing the table into its O(n) worst case and making each operation slow. Common defences in general terms: use a hash function with a secret random seed chosen at start-up, limit input sizes, or use a table design that bounds the damage of long chains. This is one more reason not to invent your own hash for untrusted input.

Try it by hand

  1. Draw 5 buckets, numbered 0 to 4Your hooks
  2. Give each letter a numberA is 1, B is 2, and so on
  3. Hash a wordAdd its letter numbers, divide by 5, keep the remainder
  4. Place the word in that bucketWrite it on the hook
  5. Try CAT, ACT and TACThey share letters, so they collide: how do you handle it?
Build a tiny hash table on paper.

A hash table trades a little memory and a good hash function for lookups in about one step.

Frequently asked questions

Is a hash table always O(1)?

Only on average, with a good hash function and a controlled load factor. The worst case is O(n).

Can two different keys have the same hash?

Yes. That is a collision, and tables are designed to cope with it.

Can I get the original key back from the hash?

A table does not need to, because it stores the key. For security hashes, being hard to reverse is a design goal.

Can I store passwords with a fast hash and a salt?

No. Use a dedicated slow password-hashing function from a vetted library.

Back to all articles