Skip to main content

Why Amortized Analysis: Smoothing Worst-Case Spikes

What This Concept Is

Amortized analysis bounds the total cost of a sequence of n operations and reports the per-operation cost as total / n. It is a way to get a tighter and more honest picture of a data structure than worst-case analysis when individual operations vary wildly in cost.

Three notions of cost, carefully distinguished:

  • Worst-case per operation: the maximum any single operation can cost
  • Average-case per operation: the expected cost under a probability distribution over inputs
  • Amortized per operation: the worst-case total cost over any sequence of n operations, divided by n

Amortized is a worst-case guarantee over sequences, not a probabilistic statement. The input is adversarial; only the distribution of cost across operations is being re-accounted. An amortized bound of O(1) does not mean "almost every operation is O(1)" in a statistical sense -- it means that no matter what sequence the adversary feeds, the total cost is O(n) and therefore the per-operation average is bounded.

The three classical proof techniques (aggregate, accounting, potential) are all equivalent in expressive power but differ in ergonomics:

  • Aggregate (concept 12): bound the total directly and divide. Simplest; works when all operations are "morally the same type."
  • Accounting (concept 13): charge each operation type a fixed amortized cost and show that the credit balance stays non-negative. Feels like banking.
  • Potential (concept 14): pick Phi(state) and compute amortized cost as actual + Delta Phi. Most general; required for complex structures like Fibonacci heaps and splay trees.

Why It Matters Here

Three structures in this module have "spiky" operations that cannot be made cheap in the worst case but are cheap on average over any sequence:

  • dynamic arrays: most push operations are O(1), but occasionally one resizes at O(n)
  • union-find with rank and path compression: find is usually short but can be long once in a while (total O(m alpha(n)))
  • Fibonacci and pairing heaps: extract-min is expensive, everything else is lazy

Amortized analysis gives these structures their correct per-operation cost. Without it, you would conclude that std::vector::push_back is O(n) and therefore useless, which is absurd. With it, you recover the true O(1) amortized bound that justifies its ubiquity.

Concrete Examples

Example 1 -- dynamic-array push-back trace. A dynamic array starts empty with capacity 1. Push 1, 2, 3, ..., 16 one at a time, doubling capacity when full. The costs look like:

op 1:  cost 1         (capacity 1 -> store)
op 2: cost 2 (copy 1, store 1)
op 3: cost 3 (copy 2, store 1)
op 4: cost 1
op 5: cost 5 (copy 4, store 1)
op 6-8: cost 1 each
op 9: cost 9 (copy 8, store 1)
op 10-16: cost 1 each

Total: 1 + 2 + 3 + 1 + 5 + 1 + 1 + 1 + 9 + 1 + 1 + 1 + 1 + 1 + 1 + 1 = 30. Amortized per-operation cost: 30 / 16 < 2 = O(1).

The individual worst-case operation (op 9) is O(n). The amortized cost is O(1). Both are true statements; they describe different things.

Example 2 -- multipop on a stack. A stack supports push(x), pop(), and multipop(k) (pop up to k items). The worst-case cost of multipop is O(n). However, each element can be popped at most once, and each was pushed at cost 1. So the total cost of n mixed operations is bounded by "pushes + pops" ≤ 2n, regardless of how the operations are interleaved. Amortized cost per operation: O(1).

This is the standard intro example that shows amortized analysis can absorb unbounded per-operation spikes into a flat per-operation bound.

Common Confusion / Misconceptions

"Amortized cost is average-case cost." No. Average-case analysis assumes a distribution over inputs; amortized analysis makes no distributional assumption and gives a guarantee over every possible operation sequence. A data structure with amortized O(1) push still has that bound against an adversary; one with average-case O(1) push only has that bound in expectation over random inputs.

"Amortized O(1) means every operation is O(1)." It does not. It means that across any sequence of n operations, total cost is O(n), even though individual operations can be much larger. A real-time system may still be blocked by a single O(n) operation despite amortized O(1).

"If I can prove the bound with aggregate, I should not use potential." Use whichever method makes the proof clearest. For splay trees and Fibonacci heaps, aggregate simply does not work -- the proof requires a potential function that tracks structural complexity. For simple counters and dynamic arrays, aggregate is the cleanest.

"Amortization works for every data structure with spiky operations." It works when the total cost can be bounded. If a single operation can be Theta(n^2) and the number of such operations is Theta(n), no amortization saves you -- the total is Theta(n^3) and the per-op amortized cost is Theta(n^2). Amortization is a tool, not a magic bullet.

How To Use It

When you see a data structure with occasional expensive operations, ask:

  1. what is the per-operation worst case?
  2. can I bound the total cost over any sequence of n operations?
  3. if yes, divide total by n to get the amortized per-operation cost
  4. choose one of three proof methods:
    • aggregate (concept 12): bound total directly, divide
    • accounting (concept 13): charge each operation a fixed amortized cost with credit to spare
    • potential (concept 14): define Phi and use amortized = actual + Delta Phi
  5. stress-test your proof with adversarial sequences (the hardest cases, not random ones)

When amortized bounds are not enough:

  • real-time systems: you need worst-case per operation, not amortized
  • interactive user-facing work: a single slow operation can still be a UX problem
  • hash tables with resize: most put is O(1) amortized, but one can stall for seconds on a very large table -- this is why Redis uses incremental rehashing

Transfer / Where This Shows Up Later

  • S5 (OS): kernel memory allocators and slab caches are justified with amortized bounds; a single slab-grow is expensive but amortized over many allocations.
  • S6 (databases): LSM-tree compactions, write-ahead-log truncation, and B-tree page-splits are all spike operations that make sense only through amortized analysis. "Write amplification" is a physical-level manifestation of the amortized accounting.
  • S7 (architecture): ADRs that choose "amortized fast" over "worst-case fast" (e.g., hash table vs trie, vector vs linked list) live and die on the user's tolerance for occasional latency spikes.
  • S8 (scale): rate limiters with token buckets, autoscaler cooldown logic, and streaming percentile estimators all express budget in amortized terms.

Check Yourself

  1. State the difference between worst-case, average-case, and amortized cost in one sentence each.
  2. Why is amortized cost still a worst-case guarantee?
  3. Give an example of a setting where an amortized O(1) bound is not acceptable.
  4. What three methods are used to prove amortized bounds?
  5. Why do competitive benchmarks often hide amortized spikes behind "p99 latency"? What does that reveal about the metric's relationship to amortization?
  6. Can a deterministic data structure with an amortized O(log n) bound ever have a single operation of Theta(n) cost?

Mini Drill or Application

  1. Consider a stack that supports push(x), pop(), and multipop(k) (pop up to k items). The worst-case cost of multipop is O(n). Bound the total cost of any sequence of n stack operations.
  2. Compute the amortized cost per operation from your total-cost bound.
  3. Argue that this bound is tight or not tight; give a sequence that achieves it.
  4. Rewrite the bound in terms of an accounting scheme: what credits would you charge push to pay for a later multipop?
  5. Implement a DynamicArray in your language of choice. Run 10^7 pushes and print the observed "average time per push" over windows of 10,000 ops; observe how the geometric spikes average out.

Read This Only If Stuck