Visual walkthrough — Python dict and set internals
By the end we will have earned every symbol in this central formula from the parent note — the average number of boxes a not-found search touches, written as a fraction that only depends on how crowded the table is: where ("how full the table is") is defined carefully in Step 5, and the left side is read as "expected probes" once "probe" is defined in Step 4. Every symbol is earned before it is used.
Step 1 — What is a "slot"? A wall of numbered boxes
WHAT. We draw a row of boxes and give each a number underneath.
WHY. A hash table is just an array. Before we can talk about "which box a key goes in", we need a name for a box and a name for its position. That name is the index.
PICTURE. Look at the row below. The numbers through are the indices. Right now every box is EMPTY (chalk outline, nothing inside).

Where does an index come from? Not from where we want the key — from the key itself. That is Step 2.
Step 2 — Turn a key into a starting index with & (m-1)
WHAT. We compute the starting index:
- — the raw hash integer of the key.
- — bitwise AND: keep a bit only where both sides have a .
- — when is a power of two, in binary is all ones in the lowest bits. For , . AND-ing with it keeps exactly the lowest 3 bits and throws the rest away.
WHY this tool and not %? We could write (the remainder after dividing by ).
For a power-of-two , h & (m-1) gives the identical answer but is one cheap CPU instruction
instead of a division. That is the only reason CPython insists is a power of two.
PICTURE. Take . In binary . The mask keeps only the last three bits . So key with hash starts at box .

If box is empty, we drop the key there and we are done in one touch. The trouble begins when someone else also lands on box .
Step 3 — A collision: two keys, one starting box
WHAT. Add a second key with hash . Its low three bits: , keep last three . Same box as the first key.
WHY it happens. The mask only looked at the low bits. Two hashes that agree on their low bits ( ends in , ends in ) collide even though their high bits differ. So we need a rule for "box is taken — now where?"
PICTURE. Box is already FULL (first key, blue). The second key (pink) arrives, sees box occupied by a different key, and must go looking elsewhere. The red arrow is the collision.

Step 4 — Probing: the perturbed jump that visits every box
Before the formula, two pieces of programmer notation used inside it:
WHAT. CPython's next-index rule is:
Term by term:
- — the box we just found occupied.
- — a cheap mixing step; multiplying by and adding walks around the boxes in a scattered (non-adjacent) order, so keys don't pile up in one run.
- — starts equal to the full hash . It carries the high bits the mask threw away in Step 2.
- — after each probe, right-shift
perturbby 5 bits (integer-divide it by ), feeding fresh high bits into the next jump. Eventuallyperturbbecomes and the pure walk takes over.
WHY this tool and not linear probing? The simple rule (walk to the next box) causes clustering: full boxes clump into long runs, and every new collision in that run has to walk the whole run. The perturbed rule mixes in the discarded high bits, so two keys that shared low bits (like and ) diverge immediately on the very next jump.
PICTURE. Continue Example 1 from the parent. Second key: , occupied. With : , low three bits . Box is EMPTY store here. The curved arrow shows the jump from to , skipping and — that's the anti-clustering scatter.

Step 5 — What "load factor" measures, and why it is the whole story
WHAT. is a single number that says "how crowded is the wall of boxes?" is empty; means 90% of boxes are taken.
WHY it is the only thing that matters for speed. When you probe, the only question that decides whether you must jump again is: "is the box I landed on occupied?" On average, the chance a random box is occupied is exactly . So — not , not alone — controls the cost. This is the hinge of the whole derivation.
PICTURE. Two walls: a sparse one (, lots of EMPTY, you almost never jump) and a crowded one (, jumps everywhere). Same math, different crowding.

Step 6 — Count the probes: a geometric series falls out
WHAT. We compute the expected number of boxes we touch for a search that ends at an EMPTY box — an unsuccessful (not-found) search. Let be that number of probes.
WHY a geometric series? By the uniform-hashing model each probe is an independent coin flip: with probability the box is occupied (jump again), with probability it is EMPTY (stop). "How many independent flips until the first EMPTY?" is the classic geometric question — that is why this tool, and not calculus, appears here.
The count, term by term.
- Probability we need probes = the first boxes were all occupied. By independence
(the model above), these events multiply: .
- — multiply by itself times; each factor is one occupied box in a row.
- Therefore, by the identity above:
PICTURE. The curve . It is nearly flat and low for small , then rockets upward as . That vertical wall is the danger zone.

Step 7 — The other quadrant: cost of a successful lookup
WHAT. We want the average probes to find a key that is present. The trick: when we look up a present key, we retrace exactly the probe path we walked when we inserted it. So the cost of finding it now equals the cost of the unsuccessful search that placed it back then.
Setting up the labels (defining ). Suppose the table currently holds present keys, and we inserted them one after another. Label them by insertion order: key number was the -th key inserted, for .
- — an insertion-order counter: is the very first key, is the most recent.
- When key was inserted, the table already held earlier keys, so its load factor at that moment was where = keys already present and = number of boxes.
Cost of inserting key . Insertion runs an unsuccessful search (it probes until EMPTY), so by Step 6 its expected probes were
Averaging over all present keys. A successful lookup picks one of the present keys at random, so its expected cost is the average of those insertion costs — a plain arithmetic mean, sum divided by count:
Turning the sum into (WHY the logarithm appears). Substitute so , and factor: The remaining sum is a stretch of the harmonic series. For large we approximate a sum of by the integral of (that is precisely the tool that converts a discrete reciprocal-sum into a closed form, because ): Putting the back in front: where is the natural logarithm — "to what power do you raise to get this number?".
WHY it is smaller than a miss. Every present key was inserted when the table was less full than it is now, so on average a hit is found faster than a miss. Sanity-check at the cap : So a hit costs about 1.6 probes versus 3 for a miss — both are small constants, which is all "O(1)" needs.
PICTURE. Both curves on one axis: the successful curve (lower) always sits under the unsuccessful curve (higher), for every .

Step 8 — The edge case: cap so the curve never explodes
WHAT. CPython triggers a resize the moment crosses two-thirds of the table: which keeps the load factor pinned at at all times. Feed that cap into the Step 6 miss formula:
- — occupied boxes (active keys plus dummies), from Step 5.
- — the trigger line; is CPython's chosen ceiling for .
- The right-hand — the largest the average miss cost is ever allowed to be.
WHY this exact tool — a cap — and not a bigger table up front? Look again at Step 6's curve (redrawn below with the safe/forbidden regions shaded). The height is flat and low on the left, then curves up and blows to infinity as . Picking the resize line at keeps us on the flat part where the height is exactly — cheap — while not wasting so much memory that most boxes sit empty. A smaller cap (say ) would be even faster but waste memory; a larger cap (say ) would save memory but push cost toward the cliff. Two-thirds is the balance point. This is where the "O(1)" of the parent note actually comes from — it is O(1) because rehashing forbids from ever reaching the steep part of the curve.
How rehashing bounds over time (the sawtooth). Inserts push up, raising toward . The instant it crosses, the table roughly doubles ( next power of two), so drops back to about , and the climb restarts. Plotted over inserts, traces a sawtooth bounded above by — never escaping to .
All the edge / degenerate cases, closed one by one:
- (empty table): . One touch — you hit EMPTY immediately. ✓
- (table nearly full): . This is forbidden by the cap — we never get here, because a resize fires at .
- Dummies dominate (many deletes, few live keys): counts dummies, so a table full of DUMMY markers still crosses and resizes, which purges the dummies. Probe chains stay short. ✓
- All keys collide (adversarial hashes, all same low bits): the uniform-hashing assumption of Step 6 breaks, chains become length , and lookup degrades to O(n). Resizing cannot fix a pathological hash — only a better hash function can. This is the true worst case, and it is why "O(1)" is an average, not a guarantee.
Recall Check the threshold arithmetic yourself
For , does inserting the 5th key resize? ::: No. . Resize fires at since . At , expected probes for a miss? ::: . At , expected probes for a hit? ::: . At ? ::: probe.
Recall Why the O(n) rehash is still O(1) amortized
A resize from to only happens after about inserts, so its O(n) cost is spread across those operations → O(1) amortized per insert. See Amortized Analysis and Big-O Notation.
The one-picture summary
Everything on one board: key hash mask to a starting box perturbed probe on collision crowding measured by miss cost (hit cost lower) capped at by resizing.
Recall Feynman: the whole walkthrough in plain words
You have a wall of numbered lockers. To store your bag, you run your name through a machine (the hash) and it spits out a big number; you keep only the last few digits so it names a real locker. If that locker is taken by someone else, a little rule sends you jumping to another locker — and the jump mixes in the digits you'd thrown away, so two people whose names started the same don't keep bumping into each other. How many lockers you have to try depends on just one thing: how full the wall is. If a quarter are full you almost never jump; if it's nearly packed you jump forever. That "how full" number is , and the average tries for a bag that isn't there is ; for a bag that is there it's a bit less, because it was filed when the wall was emptier. Since the janitor builds a bigger wall and re-files everyone whenever it passes two-thirds full, never gets past , so on average you open only about three lockers — no matter how many bags are stored. That "three, always" is what people mean by O(1).
See also: parent topic · Immutability and Hashability · Java HashMap Internals (uses chaining, not open addressing).