3.3.7 · D2Hashing

Visual walkthrough — Amortized O(1) operations

2,349 words11 min readBack to topic

Before we start, the precondition and the two words we lean on:


Step 1 — The array is a row of boxes, and it can get full

WHAT. Picture the array as a row of boxes. Each box holds one item. The number of boxes is the capacity; the number of filled boxes is the size.

WHY. We need a mental object before any math. "Full" means size = capacity — there is no empty box left, so the next push has nowhere to go.

PICTURE. In the figure, blue boxes are filled, gray boxes are empty. The moment every box turns blue, we are stuck — that is the trigger for everything that follows.

Figure — Amortized O(1) operations

Step 2 — The choice: grow by +1, or double?

WHAT. When full, we must make a bigger array and pour the old items into it. Two strategies:

  • +1 growth: make exactly one extra box each time.
  • doubling: make twice as many boxes.

WHY. This single choice decides whether the whole structure is fast or slow. We compare them side by side so the difference is visual, not abstract.

PICTURE. Top row: +1 growth — the array creeps up 1,2,3,4,5,... and it is full again immediately, so it copies again immediately. Bottom row: doubling — capacities jump 1,2,4,8,... leaving lots of free boxes (gray) after each resize, so the next resize is far away.

Figure — Amortized O(1) operations

Step 3 — Count the cheap part: every item is written exactly once

WHAT. Ignore resizing for a moment. Push items. Each push, at minimum, writes its one item into a slot.

WHY. This cost is unavoidable and simple, so we bank it first before tackling the tricky copies. Getting the easy term out of the way keeps the bookkeeping clean.

PICTURE. blue boxes light up, one per push. The counter on the side ticks .

  • = how many items we pushed.
  • "one write per push" = the size grows by exactly 1 each push, so we do exactly writes over the whole run.
Figure — Amortized O(1) operations

Step 4 — Count the expensive part: copies happen at powers of two

WHAT. With doubling from capacity 1, the array becomes full exactly when its size hits — the powers of two. At each of those moments we resize and copy everything currently inside.

WHY. We want to know how many copies pile up. Doubling means a resize at size copies items — so the copy amounts are themselves .

PICTURE. A staircase: each resize is an orange bar whose height is the number of items copied. The bars are — each twice the one before.

  • = the last and largest resize that happens before we finish, and by the definition above .
  • each term = the whole array getting copied once, at ever-larger sizes.
Figure — Amortized O(1) operations

Step 5 — The magic: that sum is less than twice the last bar

WHAT. We add up the orange bars . The result is exactly , which is less than .

WHY (this is the heart). Why does a growing sum stay small? Because each bar is double the previous one, so all the earlier bars stacked together are still shorter than the last bar. This is a Geometric Series — a sum where each term is a fixed multiple of the one before. Geometric sums are the tool that answers "if things keep doubling, does the total blow up?" — and the answer is: no, the total is dominated by the final term.

PICTURE. Stack bars on top of each other on the left. On the right stands the single bar . The left stack () is shorter than the right bar (). The whole history of copies is smaller than one more doubling.

  • = the exact total (a power of two minus one — think 0111...1 in binary). Notice it is one less than : this off-by-one is what makes the copy cost strictly below .
  • = double the largest single copy.
  • = because the largest copy can be at most , so double it is at most ; combined with the "", the copy cost is .
Figure — Amortized O(1) operations

Step 6 — Add the two piles and divide

WHAT. Total work = cheap writes + expensive copies , which is strictly less than . Amortized cost is total divided by the number of operations .

WHY. The word amortized means exactly this division: spread the whole bill across every push. Divide the total by and the 's cancel, leaving a plain constant. Because the copies are (not exactly ), the true total is , so the average is strictly below 3 — and certainly holds.

PICTURE. Two stacked bars — a tall blue " writes" and a shorter orange " copies" — merge into one bar of height just under . Slice it into equal slivers; each sliver is height at most .

  • numerator = the entire bill for the whole run — strictly under .
  • in the denominator = the number of things we're splitting the bill among.
  • = a constant upper bound — it does not grow with . That is what means (see Big-O Notation).
Figure — Amortized O(1) operations

Step 7 — Edge case: what about +1 growth? (why it fails)

WHAT. Repeat the count for +1 growth. Now a resize happens at every size , and each copies that many items. The copy bars are — an arithmetic series, not geometric.

WHY. We must show the other case so you never assume "bigger array = fine." With +1, each bar is only one taller than the last, not double — so the earlier bars are not dwarfed by the final one. They pile into a triangle.

PICTURE. A right triangle of bars . Its area is the copy cost: base , height , area . Compare against Step 5's thin geometric strip.

  • the triangle's area = adding one-taller bars fills half of an square.
  • dividing by gives amortized grows with , so not constant.
Figure — Amortized O(1) operations

Step 8 — Degenerate cases: empty and single push

WHAT. Two boundary inputs.

  • : no pushes at all → total cost 0. Amortized is undefined () but there is simply nothing to bound.
  • : one push into an empty capacity-1 array → 1 write, 0 copies (nothing to copy yet) → total 1, amortized .

WHY. A guarantee that only works for large isn't a guarantee. We check the smallest inputs so no reader hits an unshown scenario. Happily, the bound "" holds from the very first push, where cost is .

PICTURE. Left panel: an empty array, cost counter at 0. Right panel: one blue box, cost counter at 1 — no orange copy bar exists yet.

Figure — Amortized O(1) operations
Recall Check the tiny cases yourself

For , amortized cost is? ::: ✅ — one write, zero copies. For , is the array ever resized? ::: No — no pushes means no fills, no copies, total cost 0.


The one-picture summary

Everything above in a single frame: the blue writes grow like a straight line (), the orange doubling copies grow like a lower straight line (), their sum is a straight line () — while the red +1 copies curve up as a parabola (). Straight lines divided by give a constant; the parabola divided by keeps climbing.

Figure — Amortized O(1) operations
Recall Feynman: the whole walkthrough in plain words

You fill boxes with toys. Every toy you add costs 1 (you set it in a box). Sometimes a box is full and you must buy a bigger box and carry every toy over — that's the slow part. If you always buy a box twice as big, then the sizes you carry are , and here's the wonderful thing: all the earlier carries added up are still smaller than the very last carry. So the whole lifetime of carrying is less than — as if each toy was carried barely twice. Add the set-downs and the (just under) carries, spread across toys, and every toy costs a bit under 3 on average. If instead you only bought one extra box each time, you'd carry toys again and again — toys — which piles into a triangle of size , and now each toy costs a growing amount. The single idea: doubling keeps the copies a thin constant; adding lets them grow into a wall.


Connections

  • Amortized O(1) operations — the parent result this page derives visually.
  • Dynamic Arrays — the doubling array is exactly this structure.
  • Geometric Series — the Step 5 "sum stays under twice the last term" tool.
  • Big-O Notation — why a constant is and is not.
  • Potential Method — an alternate route to the same (see parent).
  • Hash Tables · Load Factor — rehashing reuses this identical count.
  • Stack with Multipop — another amortized classic using the same style of argument.