Hash tables & maps

A hash table finds a value by its key in about one step, whatever its size, by turning the key into an array index. It is behind every dictionary, map and set you use.

3 examples16 to practiceRead · 7 min
The idea

A hash table turns a key into an array index

You keep the price of every fruit a shop sells and want the price of "plum". In a list of pairs, that means checking pairs one by one: O(n). An array would answer in one step, but only for a number index, and "plum" isn't a number.

A hash table makes it one. A hash function turns the key into a number, and that number, taken mod the array's length, picks a cell. The cells are called buckets. To store a pair you put it in its key's bucket; to find a key you compute the same bucket and look only there.

"plum"hash functiona numbermod 5a bucketbucket 1"plum"hash functiona numbermod 5a bucketbucket 1

The same key always lands in the same bucket, so a lookup goes straight there.

In the pseudocode on these pages, {} makes an empty map, m[k] ← v stores a value, m[k] reads it and k in m checks for a key. Underneath, that is a hash table.

Hash functions

A good hash function spreads keys evenly and never changes its mind

A hash function for a table has three jobs:

  • always give the same answer for the same key, or stored pairs can't be found again;
  • give equal keys equal answers, even when they are different objects holding the same text;
  • spread different keys evenly across the buckets, so no bucket fills up while others stay empty.

A common recipe for strings walks the characters, multiplying the running number by a small prime and adding each character's code. The tables on this page use h ← (h × 31 + code(c)) mod size. For "kiwi" and 5 buckets:

Character

Code

h × 31 + code

mod 5

k

107

0 × 31 + 107 = 107

2

i

105

2 × 31 + 105 = 167

2

w

119

2 × 31 + 119 = 181

1

i

105

1 × 31 + 105 = 136

1

So "kiwi" goes in bucket 1. Real libraries use stronger functions than this, but the job is the same: the work depends on the key's length, not on how many items the table holds.

Collisions

Different keys will share a bucket, so each bucket holds a chain

There are far more possible keys than buckets, so two different keys will sometimes land in the same bucket. That is a collision, and it happens sooner than intuition says: in a room of just 23 people, the chance that two share a birthday is already over 50%, with 365 possible "buckets".

The most common answer is chaining: each bucket holds a short list of the pairs that landed there. Putting a key means finding its bucket, checking the chain for the key (to update it) and appending a new pair if it isn't there. Watch "plum" land in kiwi's bucket:

The other family is open addressing: every bucket holds at most one pair, and a key whose bucket is taken tries other buckets in a fixed order until it finds a free one. It avoids the chains' extra objects and keeps everything in one array. Java's HashMap uses chaining; CPython's dict uses open addressing.

Lookups

A lookup goes to one bucket and searches only its chain

Finding a key repeats the put's first half: hash the key, go to its bucket, and compare it with the keys in that chain. Keys in other buckets are never looked at.

The cost is the hash plus the length of one chain. With keys spread evenly and chains kept short, that is a constant: O(1) on average, for finding, inserting and deleting alike.

The rule

A hash table finds, inserts and deletes by key in O(1) on average, as long as the hash function spreads the keys and the table never gets too full. When many keys share a bucket, it slides back to a scan.

Growing

Keeping chains short means growing the table

The average chain length is the number of pairs divided by the number of buckets, the load factor. As pairs are added, it rises and lookups slow down. So a hash table watches it: Java's HashMap, for one, defaults to a limit of 0.75, and past it rebuilds itself with about twice as many buckets.

Growing isn't a plain copy. A key's bucket is its hash mod the number of buckets, so when that number changes, most keys belong in a different bucket. Every pair has to be hashed again and moved: a rehash.

A rehash is O(n), but like a dynamic array's copy, doubling makes it rare enough that inserts stay O(1) amortized. See Space & amortized analysis for why.

In practice

Maps and sets are hash tables you already use

Language

Map (key → value)

Set (keys only)

Python

dict

set

Java

HashMap

HashSet

JavaScript

Map

Set

C++

std::unordered_map

std::unordered_set

A hash set is the same table storing keys without values. It answers "have I seen this before?" in O(1), which is the first trick in a huge number of interview problems.

Three things to keep in mind:

  • No sorted order. Buckets follow the hash, not the keys. Python's dict and JavaScript's Map do promise to iterate in insertion order, but Java's HashMap makes no promise about order at all.
  • Don't change a key while it's in the table. Its hash would change, and the pair would sit in the wrong bucket, impossible to find.
  • The worst case is O(n). If many keys land in one bucket, by bad luck or by an attacker choosing them on purpose, every lookup scans one long chain. That is why languages such as Python randomize their string hashes each time a program starts.
Check yourself

4 questions

1. A hash table's hash function returns 7 for every key. What happens to lookups?
2. Why can't growing a hash table just copy each bucket into the same-numbered bucket of the bigger array?
3. A table has 1,000 pairs in 800 buckets and a load-factor limit of 0.75. What has gone wrong, and what should happen?
4. You put a list in a hash set as a key, then append an item to that list. What is the risk?
Takeaways

Remember this

  • A hash table turns a key into a bucket number with a hash function, so a lookup goes straight to one bucket.
  • Collisions are unavoidable; chaining keeps a short list per bucket, open addressing tries other buckets.
  • With an even spread and a low load factor, finding, inserting and deleting by key are O(1) on average.
  • Growing means rehashing every key, O(n) but rare enough to keep inserts O(1) amortized.
  • Hash tables have no sorted order and a worst case of O(n); never change a key while it's in one.
Practice

Now solve some yourself

Only this topic 0 of 15 solved

  1. Easy
    Two numbers that add up to a target
    Hash tables & maps
  2. Easy
    Count the distinct values
    Hash tables & maps
  3. Easy
    Do both halves of a word use the same letters?
    Hash tables & maps
  4. Easy
    Replace values and report the sum after each change
    Hash tables & maps
  5. Easy
    Is any value repeated?
    Hash tables & maps
  6. Easy
    Are two words rearrangements of each other?
    Hash tables & maps
  7. Easy
    Can one text be cut from another's letters?
    Hash tables & maps
  8. Easy
    Can one word be relettered into another?
    Hash tables & maps
  9. Easy
    Do words follow a letter pattern?
    Hash tables & maps
  10. Easy
    Build a hash set
    Hash tables & maps
  11. Easy
    Build a hash map
    Hash tables & maps
  12. Medium
    Group words that are rearrangements
    Hash tables & maps
  13. Medium
    Hand out unique user names
    Hash tables & maps
  14. Medium
    Longest run of consecutive values, in O(n)
    Hash tables & maps
  15. Medium
    Insert, delete and pick at random, all O(1)
    Hash tables & maps

Mixed with topics you have practiced 0 unlocked

    1 more once you have practiced Linked lists. Play one of its examples to the end and solve one of its problems to unlock them.

    Problems open on their own sites (LeetCode, Codeforces, CodeChef, AtCoder, CSES) in a new tab. Tick Solved when you get it accepted there; your ticks decide which mixed problems open next.

    Next

    Linear & binary search

    Continue