Amortized Analysis

Imagine pushing a million elements onto a growable array, one at a time. Occasionally the array runs out of room. It must be copied into a bigger block of memory. This operation costs time proportional to the array's current size. A pessimist looks at the worst single push. They see an O(n) copy looming. They conclude that a million pushes could cost O(n²) time. Yet every real dynamic-array implementation — Java's `ArrayList`, C++'s `std::vector`, Python's `list` — promises "amortized O(1) append." Production systems rely on that promise millions of times per second. Something in the pessimist's reasoning is too coarse. Amortized analysis is the mathematical toolkit that resolves this tension. It shifts the question from "what is the worst cost of one operation?" Instead it asks "what is the worst total cost of any sequence of n operations, divided by n?" This is not the same as average-case analysis. There is no probability distribution over inputs. There is no assumption about typical behavior. Amortized bounds are worst-case guarantees on sequences, proved by pure accounting. They hold for every possible sequence of operations, adversarially chosen. The idea matters because many of the most important data structures in practice have exactly this profile. Examples include dynamic arrays, splay trees, Fibonacci heaps, and union-find with path compression. These structures have occasional expensive operations that are individually costly but collectively rare. Such operations are interleaved with a majority of cheap operations. Charging each expensive operation to its true worst case wildly overestimates total running time. Amortized analysis captures the truth: expensive operations are paid for in advance by the cheap operations that preceded them. This article develops the three standard techniques for proving amortized bounds. These are the aggregate method, the accounting (banker's) method, and the potential method. It applies all three to two classic running examples. The first is growing a dynamic array by doubling. The second is the "multipop" stack operation that can remove many elements at once. Together these examples make the abstract machinery concrete. They also set up the same style of reasoning used later to analyze Disjoint-Set Union-Find.

Key takeaways
  • Amortized analysis bounds the total cost of n operations, not the worst single operation — giving a true worst-case guarantee over sequences.
  • It differs from average-case analysis: no randomness or input distribution is assumed; the bound holds for every sequence, including adversarial ones.
  • The aggregate method sums total cost across all operations and divides by n.
  • The accounting method assigns each operation a fixed "amortized charge," some of which is banked as credit to prepay future expensive operations.
  • The potential method defines a potential function Φ over the data structure; amortized cost = actual cost + ΔΦ, and Φ must never go negative.
  • Dynamic-array doubling gives amortized O(1) push/pop; stack multipop gives amortized O(1) per operation despite occasional O(k) pops.
The core insight

Amortized analysis proves that expensive operations are rare enough to be paid for by the cheap operations surrounding them, yielding a true worst-case bound per operation averaged over any sequence.

The idea

An amortized bound is a guarantee on the average cost per operation over the worst-case sequence of n operations, even though individual operations may vary wildly in cost. Formally, if a sequence of operations op1,op2,…,opnop_1, op_2, \ldots, op_n has true costs c1,…,cnc_1, \ldots, c_n, an amortized analysis produces amortized costs c^1,…,c^n\hat{c}_1, \ldots, \hat{c}_n such that for every n and every sequence of operations,

∑i=1nci  ≤  ∑i=1nc^i.\sum_{i=1}^{n} c_i \;\le\; \sum_{i=1}^{n} \hat{c}_i .

If each c^i=O(1)\hat{c}_i = O(1), we say the data structure supports the operation in amortized constant time, meaning any sequence of n operations costs O(n) total — never O(n²), no matter how the adversary interleaves cheap and expensive calls. This is fundamentally different from average-case analysis. Average-case analysis assumes a probability distribution over inputs (e.g., "assume all n! orderings are equally likely") and bounds the expected cost. Amortized analysis makes no probabilistic assumption whatsoever — it is a deterministic worst-case statement about sequences, proved by accounting rather than probability. A data structure with amortized O(1) operations can have some individual operations that cost Θ(n); the guarantee is only that such expensive operations cannot happen often enough, relative to cheap ones, to blow up the total. Three techniques prove amortized bounds, in increasing order of generality and rigor: 1. Aggregate method — directly compute the total cost T(n)T(n) of the worst-case sequence of n operations, then set c^i=T(n)/n\hat{c}_i = T(n)/n for all i. Simple, but assigns the same amortized cost to every operation regardless of type, and requires a global argument. 2. Accounting (banker's) method — assign each operation type a fixed amortized charge. When the charge exceeds the true cost, the surplus is stored as credit on specific objects in the structure; when true cost exceeds the charge, the deficit is paid using stored credit. The invariant "credit never goes negative" must hold at all times. 3. Potential method — define a potential function Φ mapping data-structure states to non-negative reals, with Φ(initial) = 0 (or a known baseline). The amortized cost of operation i is c^i=ci+Φ(Di)−Φ(Di−1)\hat{c}_i = c_i + \Phi(D_i) - \Phi(D_{i-1}). This is the most flexible and the standard tool for advanced structures, because Φ need not correspond to physical "credit on objects" — it can be any potential-energy-like function of the whole structure. All three methods must yield a valid upper bound on true total cost; the choice among them is a matter of which is easiest to construct and verify for a given structure.

How it works

Running example 1: dynamic-array doubling

A dynamic array starts with capacity 1. On `push`, if the array is full, allocate a new array of double the capacity, copy all existing elements over, then insert the new element. This is the strategy behind `std::vector` and Java's `ArrayList`. ``` DynamicArray: capacity = 1 size = 0 A = new array of length capacity PUSH(x): if size == capacity: capacity = 2 * capacity A' = new array of length capacity for i = 0 to size - 1: A'[i] = A[i] // copy: costs Θ(size) A = A' A[size] = x size = size + 1 ``` A single `push` that triggers a resize costs Θ(n) (copying n elements). If resizes always happened, n pushes would cost Θ(n²). But resizes are geometrically rare: they occur only when size is a power of 2 (1, 2, 4, 8, ...), so among n pushes there are only ⌊log⁡2n⌋+1\lfloor \log_2 n \rfloor + 1 resizes, and their costs form a geometric series 1+2+4+⋯+n≤2n1 + 2 + 4 + \cdots + n \le 2n. Total cost is Θ(n), giving amortized O(1) per push — proved rigorously below by all three methods.

Running example 2: stack with multipop

A stack supports `push` (O(1)), `pop` (O(1)), and `multipop(k)`, which pops min(k, current size) elements in one call, costing O(min(k, size)). ``` MULTIPOP(S, k): while not S.empty() and k > 0: S.pop() k = k - 1 ``` A single `multipop(k)` can cost Θ(n) if the stack has n elements. But an element can only be popped once after it is pushed — so across any sequence of n push/pop/multipop operations, the total number of pop operations (whether from `pop` or `multipop`) cannot exceed the total number of pushes, which is at most n. Hence total cost across n operations is O(n), giving amortized O(1) per operation.

Aggregate method applied

Dynamic array. Let nn pushes occur, starting from capacity 1. A resize happens when inserting the (2j+1)(2^j + 1)-th element for j=0,1,2,…j = 0, 1, 2, \ldots, and the copy at that point moves 2j2^j elements. The total cost of all resizes over n pushes is at most

∑j=0⌊log⁡2n⌋2j  <  2⌊log⁡2n⌋+1  ≤  2n.\sum_{j=0}^{\lfloor \log_2 n \rfloor} 2^j \;<\; 2^{\lfloor \log_2 n \rfloor + 1} \;\le\; 2n .

Adding the n unit-cost insertions themselves, total cost T(n)≤2n+n=3nT(n) \le 2n + n = 3n. Amortized cost per push is T(n)/n≤3=O(1)T(n)/n \le 3 = O(1).

Multipop. Each element, once pushed, can be popped at most once (by `pop` or by `multipop`). Over n operations there are at most n pushes, hence at most n total pop-steps across all `multipop` and `pop` calls combined. Total cost T(n)≤n (pushes)+n (pop-steps)=2n=O(n)T(n) \le n\ (\text{pushes}) + n\ (\text{pop-steps}) = 2n = O(n), so amortized cost per operation is O(1).

Accounting method applied

Dynamic array. Charge each `push` an amortized cost of c^=3\hat{c} = 3 (three "coins"), regardless of whether it triggers a resize.

  • 1 coin pays for inserting the new element itself.
  • 2 coins are stored as credit on the new element.

Invariant: immediately after any push that does not trigger a resize, every element currently in the second half of the array (i.e., every element inserted since the last resize) carries exactly 2 coins of credit. When the array becomes full and the next push triggers a resize from capacity C to 2C, there are C elements in the array, and the C elements inserted since the previous resize (in fact all of them, since the whole array was filled by them) each carry 2 coins — a total of 2C≥C2C \ge C coins available, exactly enough to pay the Θ(C) copying cost. The credit is spent to zero, and the process restarts identically after the resize. Credit is never negative, so the accounting is valid, and the amortized charge of 3 per push is an honest upper bound.

Multipop. Charge each `push` an amortized cost of 2: 1 coin pays for the push itself, 1 coin is stored as credit on the pushed element. Charge `pop` and each pop-step inside `multipop` an amortized cost of 0 — the actual unit cost of popping an element is paid entirely by spending the 1 credit stored on it when it was pushed. Since every element has exactly 1 credit when pushed and every pop of that element spends exactly 1 credit, credit never goes negative. Total amortized cost over n operations is at most 2n=O(n)2n = O(n).

Potential method applied

Dynamic array. Define the potential

Φ(D)=2⋅size(D)−capacity(D).\Phi(D) = 2 \cdot \text{size}(D) - \text{capacity}(D).

Check Φ≥0\Phi \ge 0 whenever the array is at least half full (true just after any resize, and size only grows relative to a fixed capacity between resizes, so Φ≥0\Phi \geq 0 is maintained as an invariant we verify below), and Φ=0\Phi = 0 right after a resize (since size = capacity/2 just after doubling makes 2⋅(C/2)−C=02\cdot(C/2) - C = 0... more precisely we track it through the push itself). Case 1 — push without resize (size < capacity before push): true cost ci=1c_i = 1.

ΔΦ=[2(size+1)−capacity]−[2 size−capacity]=2.\Delta\Phi = [2(\text{size}+1) - \text{capacity}] - [2\,\text{size} - \text{capacity}] = 2.

Amortized cost c^i=ci+ΔΦ=1+2=3\hat c_i = c_i + \Delta\Phi = 1 + 2 = 3. Case 2 — push with resize (size = capacity before push): true cost ci=size+1c_i = \text{size} + 1 (copy size elements, then insert). Before the push, Φ=2 size−capacity=2 size−size=size\Phi = 2\,\text{size} - \text{capacity} = 2\,\text{size} - \text{size} = \text{size} (since capacity = size here). After the push, new capacity =2 size= 2\,\text{size}, new size =size+1= \text{size}+1, so

Φafter=2(size+1)−2 size=2.\Phi_{\text{after}} = 2(\text{size}+1) - 2\,\text{size} = 2.
ΔΦ=2−size.\Delta\Phi = 2 - \text{size}.
c^i=ci+ΔΦ=(size+1)+(2−size)=3.\hat c_i = c_i + \Delta\Phi = (\text{size}+1) + (2 - \text{size}) = 3.

Both cases give amortized cost exactly 3 = O(1), matching the accounting method exactly (the potential Φ is precisely twice the "credit" stored in the accounting proof). Since Φ≥0\Phi \ge 0 always and Φ0=0\Phi_0 = 0 initially, ∑c^i≥∑ci\sum \hat c_i \ge \sum c_i, so total true cost is O(n)O(n).

Multipop. Define Φ(D)=size(D)\Phi(D) = \text{size}(D), the number of elements currently on the stack. For `push`: ci=1c_i = 1, ΔΦ=1\Delta\Phi = 1, so c^i=2\hat c_i = 2. For `multipop(k)` that pops k′=min⁡(k,size)k' = \min(k, \text{size}) elements: ci=k′c_i = k', ΔΦ=−k′\Delta\Phi = -k', so c^i=k′−k′=0\hat c_i = k' - k' = 0. In both cases c^i=O(1)\hat c_i = O(1), and since Φ≥0\Phi \ge 0 always, total true cost over n operations is O(n)O(n). ``` // General template for potential-method proofs DEFINE potential function Φ(D) ≥ 0 for all reachable states D VERIFY Φ(D_0) = 0 (or a known constant) FOR each operation i with true cost c_i, transforming D_{i-1} to D_i: ĉ_i = c_i + Φ(D_i) - Φ(D_{i-1}) SHOW ĉ_i = O(f(n)) using the definitions of c_i and Φ CONCLUDE: Σ c_i ≤ Σ ĉ_i - Φ(D_n) + Φ(D_0) ≤ Σ ĉ_i = O(n · f(n)) ```

Figure
Amortized cost of n pushes: true cost spikes at each doubling, but potential absorbs the spikes
Dynamic array: true cost vs. potential Φ over 16 pushes push index i (1..16) cost amortized ĉ = 3 plain push (cost 1) resize push (cost = old size + 1)

Complexity & correctness

Time complexity — dynamic array. Any sequence of n `push` operations runs in O(n) total time, i.e., amortized O(1) per push, by any of the three methods above. The bound is tight in the sense that Θ(n) total work is unavoidable (each element must be written at least once), and it is achieved because the geometric-doubling strategy makes resize costs form a convergent geometric sum (1+2+4+⋯+n<2n1+2+4+\cdots+n < 2n) rather than an arithmetic one. This is precisely why doubling is used instead of, say, incrementing capacity by a fixed constant c: with additive growth, resizes happen every c pushes and each costs Θ(current size), giving total cost Θ(n2/c)\Theta(n^2/c) — genuinely quadratic, not amortized constant. Geometric growth is essential, not incidental.

Time complexity — multipop. Any sequence of n stack operations (`push`, `pop`, `multipop`) runs in O(n) total time, amortized O(1) per operation. The key invariant is the conservation of pops: the number of successful pop-steps (via `pop` or inside `multipop`) across the whole sequence cannot exceed the number of prior pushes, because an element must be pushed before it can be popped, and each element is popped at most once. This is the combinatorial fact underlying all three proofs above.

Correctness of the accounting method in general. The method is valid whenever one exhibits (a) an amortized charge c^i\hat c_i per operation, and (b) a credit-invariant showing the running "bank balance" Bi=∑j≤i(c^j−cj)B_i = \sum_{j\le i}(\hat c_j - c_j) never goes negative. If Bi≥0B_i \ge 0 for all i, then ∑j≤icj≤∑j≤ic^j\sum_{j \le i} c_j \le \sum_{j \le i} \hat c_j for all i — the defining property of a valid amortized bound. The proof obligation is exactly maintaining the credit invariant under every operation type, which is what the "2 coins per new element" argument does for the dynamic array.

Correctness of the potential method in general. Telescoping the definition c^i=ci+Φ(Di)−Φ(Di−1)\hat c_i = c_i + \Phi(D_i) - \Phi(D_{i-1}) over i=1,…,ni = 1, \ldots, n gives

∑i=1nc^i=∑i=1nci+Φ(Dn)−Φ(D0).\sum_{i=1}^n \hat c_i = \sum_{i=1}^n c_i + \Phi(D_n) - \Phi(D_0).

Rearranging,

∑i=1nci=∑i=1nc^i−Φ(Dn)+Φ(D0)≤∑i=1nc^i,\sum_{i=1}^n c_i = \sum_{i=1}^n \hat c_i - \Phi(D_n) + \Phi(D_0) \le \sum_{i=1}^n \hat c_i,

provided Φ(Dn)≥Φ(D0)\Phi(D_n) \ge \Phi(D_0) — which is guaranteed if Φ(D0)=0\Phi(D_0) = 0 (or minimal) and Φ≥0\Phi \ge 0 always. This telescoping argument is the entire proof; the only work is defining a Φ under which each individual c^i\hat c_i is small. The accounting method's "credit" is exactly Φ\Phi in disguise (credit-per-element summed equals Φ in the dynamic-array example: Φ=2 size−capacity\Phi = 2\,\text{size} - \text{capacity} equals total stored credit).

Honest tradeoffs. Amortized bounds guarantee total sequence cost, not per-call latency: a single call can still take Θ(n) time in the worst case (a resize, or a multipop of the whole stack). This matters in real-time or latency-sensitive systems — a game engine that cannot tolerate an occasional 5 ms stall from a vector resize may need a data structure with genuine worst-case O(1) operations (e.g., incremental/lazy resizing schemes), not merely amortized ones. Amortized analysis also says nothing under adversarial cancellation attacks against a shared, mutable structure used concurrently — the credit/potential argument assumes a single sequential thread of operations; concurrent access requires separate reasoning.

Worked example

Trace a dynamic array starting empty (capacity 1) through 10 pushes, then two multipops, tracking true cost, potential Φ=2⋅size−capacity\Phi = 2\cdot\text{size} - \text{capacity}, and amortized cost c^=c+ΔΦ\hat c = c + \Delta\Phi.

Opsize before→aftercapacity before→aftertrue cost ccΦ beforeΦ afterc^=c+ΔΦ\hat c = c+\Delta\Phi
push(1)0→11→11012
push(2)1→21→2 (resize)1+1=2123
push(3)2→32→4 (resize)2+1=3223
push(4)3→44→41243
push(5)4→54→8 (resize)4+1=5423
push(6)5→68→81243
push(7)6→78→81463
push(8)7→88→81683
push(9)8→98→16 (resize)8+1=9823
push(10)9→1016→161243

Sum of true costs: 1+2+3+1+5+1+1+1+9+1=251+2+3+1+5+1+1+1+9+1 = 25. Sum of amortized costs: 2+3+3+3+3+3+3+3+3+3=292+3+3+3+3+3+3+3+3+3 = 29. Indeed 25≤2925 \le 29, and 29/10=2.9≈329/10 = 2.9 \approx 3 per push as the theory predicts (the first push is slightly cheaper because Φ(D0)=0\Phi(D_0)=0 exactly and capacity starts already at 1, a minor boundary effect). Now apply `multipop(4)` to the 10-element stack (size 10, capacity 16), popping elements 10, 9, 8, 7 (LIFO order):

Opsize before→aftertrue cost ccΦ beforeΦ afterc^=c+ΔΦ\hat c = c+\Delta\Phi
multipop(4)10→644-40

Using Φ=size\Phi = \text{size} for the stack-credit view (not the array-capacity view — multipop's potential is defined purely on stack size, since here we assume the underlying array need not shrink): c=4c = 4 (four real pops), ΔΦ=6−10=−4\Delta\Phi = 6 - 10 = -4, so c^=4+(−4)=0\hat c = 4 + (-4) = 0. The four credits banked during pushes 7–10 (one credit per pushed element, per the accounting-method construction) exactly pay for the four pops — no new charge is levied. A second `multipop(20)` on the now-6-element stack pops all 6 remaining elements: c=6c = 6, ΔΦ=0−6=−6\Delta\Phi = 0 - 6 = -6, c^=0\hat c = 0 again. Every multipop, however large, has amortized cost 0, confirming the earlier general argument.

Common pitfalls & traps

PitfallWhy it's wrongFix
Confusing amortized with average-caseAmortized bounds hold for every sequence, deterministically; average-case needs a distribution over inputsState clearly which guarantee is being made; amortized ⇒ no randomness needed
Reporting a single expensive call's cost as "the" time complexityAn O(n) resize does happen, but rarely — reporting O(n) per push is correct-but-misleading for sequencesAlways specify "amortized" when quoting O(1) push, and note worst-case-per-call separately
Using additive (fixed-increment) array growth and still claiming O(1) amortized pushAdditive growth causes Θ(n) resizes each costing Θ(current size), giving Θ(n²/c) total, not O(n)Growth factor must be a constant > 1 (doubling, 1.5×, etc.), not a constant increment
Forgetting to shrink (or shrinking too aggressively) on `pop`, causing "thrashing"Halving capacity exactly when size drops to capacity/2 can oscillate push/pop/push/pop between resize boundaries, giving Θ(n) per pairShrink only when size falls to capacity/4, giving hysteresis and restoring amortized O(1)
Letting potential Φ go negative at some reachable stateIf Φ(Di)<0\Phi(D_i) < 0 is possible, the telescoping inequality ∑ci≤∑c^i\sum c_i \le \sum \hat c_i can failAlways explicitly verify Φ≥0\Phi \ge 0 for every reachable state, not just typical ones
Assuming amortized bounds protect against worst-case latencyA single multipop or resize can still take Θ(n) real time on that one callFor hard real-time systems use structures with true worst-case bounds (e.g., incremental resizing)

Pioneers & history

Amortized analysis crystallized as a named technique in the study of self-adjusting data structures in the 1980s, though the underlying accounting intuition — "some operations prepay for future ones" — appears implicitly in earlier data-structure analyses (e.g., binary counters and union-find). The potential-method formalization is most closely associated with Robert Tarjan, who used it to analyze splay trees, Fibonacci heaps, and path compression in union-find, turning ad hoc credit arguments into a systematic, reusable mathematical framework.

Origins · 1 minRobert Endre Tarjan and how this was discovered
RE
Robert Endre Tarjan
born 1948 · Bell Labs and Princeton, foundational work on data-structure analysis

Tarjan, working with Daniel Sleator, developed the potential-method formalism for amortized analysis and applied it to splay trees (1985) and Fibonacci heaps (1987, with Michael Fredman), showing that operations with occasionally expensive worst cases could still guarantee strong aggregate performance. His 1986 Turing Award (shared with John Hopcroft) recognized broader contributions to algorithm and data-structure design, including depth-first search applications and union-find analysis — all areas where amortized reasoning became indispensable.

History
1965
— early binary-counter and stack analyses implicitly use amortized-style credit arguments without the formal name.
1978
— amortized analysis is used informally in analyzing balanced-tree rebalancing costs.
1985
— Sleator and Tarjan introduce splay trees and formalize the potential method to prove their amortized O(log n) bound.
1987
— Fredman and Tarjan introduce Fibonacci heaps, using amortized analysis to achieve O(1) amortized decrease-key.
1990
s onward — amortized analysis becomes a standard chapter in algorithms curricula and a routine tool for analyzing dynamic arrays, hash-table resizing, and union-find.

Where this matters

Every mainstream dynamic-array implementation — C++ `std::vector`, Java `ArrayList`, Python `list`, Go slices, Rust `Vec` — relies on geometric-growth resizing precisely because it delivers amortized O(1) `push_back`/`append`, making these structures practical defaults for general-purpose programming rather than niche tools. Hash tables built on dynamic arrays (essentially every language's built-in hash map or dictionary) use the identical doubling argument to justify amortized O(1) insertion despite occasional full-table rehashes. Stack-based algorithms that use multipop-like batching — for instance the classic "largest rectangle in histogram" algorithm, or bracket-matching parsers that pop multiple unmatched symbols at once — rely on exactly the conservation-of-pops argument to claim overall linear time. Text editors and version-control systems that maintain undo/redo stacks with batch-clearing operations use the same reasoning. The potential-method machinery introduced here is also the direct prerequisite for analyzing more advanced amortized structures: splay trees, Fibonacci heaps, and — foreshadowing a natural next topic — Disjoint-Set Union-Find, where path compression and union-by-rank combine to give a nearly-constant (inverse-Ackermann) amortized cost per operation, one of the most celebrated results in data-structure analysis. Understanding amortized analysis is also foundational for reasoning about Big-O Notation claims that appear in library documentation, and it builds directly on intuition from Arrays and Dynamic Arrays.

Ask the tutor: How does the potential-method proof change when a Fibonacci heap's decrease-key operation is added to the mix, and why does that require a more elaborate potential function than the dynamic array's?

Is amortized O(1) the same guarantee as worst-case O(1)?

No. Worst-case O(1) means every single operation takes constant time, with no exceptions. Amortized O(1) means the total cost of any n operations is O(n), so the average cost per operation is O(1), but any individual operation — such as a dynamic-array resize or a full multipop — can still take Θ(n) time. If an application needs a hard guarantee on every single call (real-time audio processing, safety-critical control loops), amortized bounds are not sufficient on their own; one needs either a worst-case-O(1) structure or an amortized structure combined with techniques that spread the expensive work across several prior operations (de-amortization).

Why is the aggregate method sometimes considered weaker than the potential method?

The aggregate method computes one global total cost T(n)T(n) and divides by n, assigning the same amortized cost to every operation regardless of its type. This is fine when all operations are structurally similar (as with pushes in a dynamic array), but it cannot easily distinguish, say, "amortized cost of insert" from "amortized cost of delete" when they behave very differently. The potential method assigns a per-operation amortized cost c^i=ci+ΔΦ\hat c_i = c_i + \Delta\Phi tailored to each operation type, which is essential for structures with several operations of very different character, like Fibonacci heaps (insert, decrease-key, extract-min all need separate, different bounds).

Does the accounting method's "credit" have to correspond to real memory or money?

No — credit is a pure bookkeeping device, not a physical resource. In the dynamic-array proof, "2 coins per element" is an abstract accounting convention chosen specifically to make the invariant (credit funds every resize) hold; nothing in the actual program stores these coins anywhere. The only requirement is that the bookkeeping be consistent: every operation's amortized charge must be assigned before its true cost is known to the analysis, and the running credit balance must never be allowed to go negative. Different choices of credit assignment can prove the same bound; 2 coins per element is simply a convenient, minimal choice for doubling arrays.

How does shrinking a dynamic array on pop interact with amortized analysis?

If you shrink capacity to exactly match size (e.g., halve capacity whenever size drops to capacity/2), an adversary can alternate push, pop, push, pop right at that boundary, triggering a resize on every operation — destroying the O(1) amortized bound and producing Θ(n) per operation in the worst case. The standard fix uses hysteresis: shrink capacity by half only when size falls to one-quarter of capacity, not one-half. This creates a buffer zone where many pushes or pops must occur before another resize is possible, restoring the geometric-series argument and the amortized O(1) bound in both directions.

Can every worst-case bound be converted into an amortized one, or vice versa?

Not directly — they answer different questions. A worst-case O(f(n)) bound per operation trivially implies an amortized O(f(n)) bound (just charge every operation its worst case), but the converse is false in general: an amortized O(1) structure can have individual operations costing Θ(n). Some amortized structures can be de-amortized — restructured to spread the expensive work across several prior calls so that every single call becomes worst-case bounded (this is a real, nontrivial technique used for example in real-time queue implementations), but de-amortization typically adds constant-factor overhead and implementation complexity, so it's used only when hard per-call bounds are actually required.

Why does the multipop example matter beyond stacks?

The "conservation" argument — each item can only be removed once after being added, so total removals across a sequence are bounded by total additions — recurs throughout algorithm design. It is the core reason algorithms like histogram-based rectangle problems, certain sliding-window techniques, and monotonic-stack/deque algorithms run in overall linear time despite loops that look like they could be O(n²) (an inner while-loop popping many elements per outer iteration). Recognizing this pattern — "each element is pushed once and popped at most once" — lets you certify linear-time behavior for a whole family of two-pointer and stack-based algorithms without a separate potential-function argument each time.

How does amortized analysis foreshadow the union-find analysis?

Union-Find with path compression and union-by-rank has operations whose individual worst-case cost can be Θ(log n), yet a careful potential-function argument (using a hierarchy based on iterated logarithms) shows the amortized cost per operation is O(α(n))O(\alpha(n)), where α is the inverse Ackermann function — effectively constant for any n that could ever be represented in physical memory. This is proved with exactly the same potential-method machinery introduced here, just with a cleverer, multi-level potential function; the dynamic-array and multipop examples are the simplest instances of the technique that scales up to this much more delicate and celebrated result.

Before this

Where this leads