How a hash map keeps lookups fast on average

Arrays, strings and hash maps Mid-level Python 3.13+ Reviewed By Best Latest Apps

Pick the fix

A service builds a dict from user-chosen string keys and slows to a crawl. What is the most likely cause?

Options

Choose an answer, then open Show answer below to check it.

Show answer

Answer: A Many keys collide, so lookups scan long chains

Right: crafted collisions push each lookup towards O(n).

That is why CPython salts string hashes per process: an attacker cannot predict which keys collide.

Why not the others?
  • B Dicts are O(log n) per lookup. Lookups are O(1) on average; they are not tree-based.
  • C Strings cannot be dict keys. Strings are the most common dict keys.
Keys 3, 11, 19, 6, 14 and 22 go into 4 buckets by key mod size; the table doubles to 8 when the load would pass 0.75 and every key moves.

Illustrative

Toy table: bucket = key % size, grows by doubling at load 0.75. Real dicts use open addressing.

Text version

Grow at 0.75:

  1. 0: –; 1: –; 2: –; 3: 3. Insert 3 into bucket 3
  2. 0: –; 1: –; 2: –; 3: 3, 11. Insert 11 into bucket 3
  3. 0: –; 1: –; 2: –; 3: 3, 11, 19. Insert 19 into bucket 3
  4. 0: –; 1: –; 2: –; 3: 3, 11, 19; 4: –; 5: –; 6: –; 7: –. Load would pass 0.75: grow to 8 buckets, move 3 keys
  5. 0: –; 1: –; 2: –; 3: 3, 11, 19; 4: –; 5: –; 6: 6; 7: –. Insert 6 into bucket 6
  6. 0: –; 1: –; 2: –; 3: 3, 11, 19; 4: –; 5: –; 6: 6, 14; 7: –. Insert 14 into bucket 6
  7. 0: –; 1: –; 2: –; 3: 3, 11, 19; 4: –; 5: –; 6: 6, 14, 22; 7: –. Insert 22 into bucket 6

Buckets: 8, moved: 3, longest: 3.

Never grow:

  1. 0: –; 1: –; 2: –; 3: 3. Insert 3 into bucket 3
  2. 0: –; 1: –; 2: –; 3: 3, 11. Insert 11 into bucket 3
  3. 0: –; 1: –; 2: –; 3: 3, 11, 19. Insert 19 into bucket 3
  4. 0: –; 1: –; 2: 6; 3: 3, 11, 19. Insert 6 into bucket 2
  5. 0: –; 1: –; 2: 6, 14; 3: 3, 11, 19. Insert 14 into bucket 2
  6. 0: –; 1: –; 2: 6, 14, 22; 3: 3, 11, 19. Insert 22 into bucket 2

Buckets: 4, moved: 0, longest: 3.

30-second answer

A hash map turns a key into a number, maps it to a slot, and checks only the few keys that landed there. The table grows as it fills, so slots stay short and lookups are O(1) on average. Many keys colliding in one slot, from a poor hash or crafted input, degrades a lookup towards O(n).

Practise saying it · 60 s. No recording, just a timer

Explain why hash map lookups are O(1) on average and what breaks that.

Say your answer out loud, then check which of these points you covered:

  • Hash to slot, check few keys
  • Resize keeps load bounded
  • Collisions and hash flooding

Deep dive

Growth keeps the load (keys per slot) under a fixed limit; each growth rehashes every key, but it happens rarely enough that inserts stay O(1) amortized.

CPython's dict uses open addressing: a collision probes other slots in a fixed pseudo-random order instead of chaining a list.

String hashing is randomised per process (PYTHONHASHSEED), which blocks attackers from precomputing colliding keys.

Collisions make one slot long Ran on Python 3.13.5 · mypy --strict
Python 3.13
class SameHash:
    def __init__(self, v: int) -> None:
        self.v = v

    def __hash__(self) -> int:
        return 7

    def __eq__(self, o: object) -> bool:
        return isinstance(o, SameHash) and o.v == self.v


d = {SameHash(i): i for i in range(1000)}
print(len(d), d[SameHash(999)])

Common mistakes

  • Saying lookups are O(1) without 'on average'.
  • Using mutable objects as keys and then changing them, which loses the entry.

Senior take & follow-ups

The words that matter are 'average', 'amortized' and 'collisions'. Mention hash flooding and randomised hashing to show you know the worst case is real.

Follow-up questions

Think of your answer first, then open the hint.

  1. Why must equal keys have equal hashes?

    Show hint

    The lookup only compares keys inside one slot; equal keys in different slots would never meet.

  2. What does Java do when one bucket gets long?

    Show hint

    Since Java 8, a long bucket turns into a balanced tree, capping that bucket at O(log n).

Similar on Android: Data classes Kotlin data classes generate equals and hashCode from their properties; in Python you write __eq__ and __hash__ yourself or use a frozen dataclass.

See it animated on iPhone (Hash Table in DSA Master - Data & Algorithms)

What each operation costs: Arrays · Dynamic Arrays · Hash Table · Hash Functions

From the author

DSA Master - Data & Algorithms

Free on iPhone and iPad: watch 50+ data structures and algorithms step through animations, run the code in the playground, and try the daily challenge.

Suggest a fix if something here is wrong or out of date.