Amortized Analysis: Accounting and Potential Methods
Imagine a dynamic array that doubles in size whenever it fills up. A single push operation can occasionally trigger a copy of the entire array. This costs time proportional to its length. Yet anyone who has used such a structure knows pushes feel cheap on average. The Amortized Analysis: The Aggregate Method can tell us the average cost across a sequence. But it says nothing about individual operations. It can also be clumsy to apply when different operations have genuinely different costs. We need tools that assign a cost to each operation. This cost must be both locally meaningful and globally accurate. The accounting method and the potential method solve exactly this problem. They let us reason about a data structure operation by operation. They prove that even though some operations are expensive, they are always "paid for in advance." This advance payment comes from cheaper operations that preceded them. This is not an accident of bookkeeping. It reflects real structural invariants in the data structure. Examples include the number of elements available to redistribute work, or the number of bits that must eventually flip. These methods matter because real systems are built from sequences of operations, not isolated ones. Consider a hash table resized dynamically, or a union-find structure processing millions of unions. Consider also a splay tree serving a stream of lookups, or a binary counter incremented repeatedly in a scheduling loop. In every case, the worst-case single operation bound is pessimistic and misleading. The amortized bound instead captures what actually happens over the long run. Compilers, standard libraries (`std::vector`, Java's `ArrayList`), persistent data structures, and network buffer implementations all rely on amortized guarantees. They do not rely on worst-case-per-operation guarantees. This article gives a rigorous treatment of the two dominant proof techniques for amortized bounds. These are the accounting (banker's) method, which assigns credits to individual objects, and the potential method, which defines a single scalar function of the entire data structure's state. We derive both from first principles, and prove their equivalence in expressive power. We work through the dynamic array and binary counter in full detail. We also highlight the subtle traps. These include sign errors in potential functions, credits that go negative, and confusing amortized bounds with worst-case or average-case bounds.
- Amortized analysis bounds the total cost of a sequence of operations, divided evenly across all operations, even when individual costs vary wildly.
- The accounting method assigns each operation an amortized cost; the surplus (amortized − actual) becomes credit stored on objects, which must never go negative.
- The potential method defines a potential function Φ over the data structure's state; amortized cost equals actual cost plus the change in potential.
- Both methods prove the same class of bounds — they are formally interchangeable, with Φ generalizing total stored credit.
- Classic examples: dynamic array doubling (O(1) amortized push), binary counter increment (O(1) amortized increment), and multi-pop stacks.
- The methods require designing an invariant (Φ ≥ 0, credit ≥ 0) that is preserved by every operation — this invariant is the proof.
An amortized bound is proved by showing that some conserved, non-negative quantity — stored credit or potential — always covers the "debt" of expensive future operations.
The idea
Amortized analysis assigns a cost to each operation in a sequence so that the sum of assigned costs upper-bounds the sum of actual costs, even though individual assigned costs need not match individual actual costs. The aggregate method computes this bound by analyzing the whole sequence at once and dividing by the number of operations. It is often the simplest first attack, but it gives only an average over the whole sequence and provides no structural explanation of why the bound holds, and it becomes unwieldy when operations have heterogeneous types (e.g., `push`, `pop`, `multipop` with different natural costs). The accounting method fixes this by assigning an amortized cost to the -th operation, chosen by the analyst, such that whenever exceeds the true cost , the surplus is stored as credit on specific objects in the structure (e.g., "each array slot holds 1 credit after being written"). Later, when an operation's true cost exceeds its amortized cost, the deficit is paid for by spending previously stored credit. The central invariant that must be maintained is:
If this invariant holds after every operation, then summing over the whole sequence gives , because the credit balance (which starts and never dips below 0) absorbs exactly the gap between amortized and actual costs. Hence is a valid upper bound on total actual cost, and the amortized cost per operation is simply . The potential method abstracts this idea one level further. Instead of tracking credit on individual objects, define a single real-valued function of the entire state of the data structure, called the potential. Let be the initial structure and the structure after the -th operation. Define the amortized cost of operation as
Summing telescopically over operations:
If we choose so that and for all , then , which gives — exactly the bound we want. The potential plays the role of "total credit in the bank"; it need not correspond to any physically stored tokens, only to a mathematical accounting quantity satisfying non-negativity.
How it works
Designing an accounting-method proof. The recipe has four steps: 1. Identify a natural unit of "work debt" in the structure (e.g., an unwritten array slot, a 1-bit in a counter, an element on a stack). 2. Propose an amortized cost for each operation type, usually a small constant or a simple expression. 3. Verify, operation by operation, that correctly deposits or withdraws credit against the objects you identified, and that no object's credit balance (nor the total) goes negative. 4. Conclude , giving the amortized bound per operation.
Designing a potential-method proof. The recipe: 1. Define as an explicit function of the data structure's state — a count, a sum, a measure of "disorder" or "imbalance." 2. Check (or more generally, that never drops below its initial value — some presentations only require for all ). 3. For each operation type, compute symbolically, in terms of the state just before the operation. 4. Show this simplifies to a clean bound (e.g., ), for every possible state, not just typical ones — this is the crux of correctness. ```text PSEUDOCODE: Potential-method template for proving an amortized bound Input: a sequence of operations op_1, ..., op_n on structure D, with real costs c_1,...,c_n Define: a potential function Phi(D) computable from D's current state procedure ANALYZE-SEQUENCE(D_0, op_1..op_n): Phi_prev <- Phi(D_0) assert Phi_prev == 0 // or >= baseline, per chosen convention total_actual <- 0 total_amortized <- 0 for i = 1 to n: c_i <- true cost of applying op_i to D_{i-1} // measured, e.g. #elementary steps D_i <- result of applying op_i to D_{i-1} Phi_i <- Phi(D_i) delta <- Phi_i - Phi_prev c_hat_i <- c_i + delta // this is the amortized cost, derived not assumed total_actual <- total_actual + c_i total_amortized <- total_amortized + c_hat_i assert Phi_i >= 0 // potential invariant: must hold for EVERY reachable D_i Phi_prev <- Phi_i // Telescoping identity (always true by construction): // total_amortized = total_actual + Phi_n - Phi_0 // Since Phi_n >= 0 = Phi_0, we get: // total_actual <= total_amortized return total_amortized // valid upper bound on total_actual ``` ```text PSEUDOCODE: Dynamic array PUSH, analyzed by both methods side by side state: array A of capacity cap, size n (initially cap = 1, n = 0) procedure PUSH(x): if n == cap: cap' <- 2 cap allocate new array A' of capacity cap' copy all n elements from A to A' // costs n elementary copy operations A <- A', cap <- cap' A[n] <- x n <- n + 1 // true cost c_i = 1 (the write) + n if a resize just happened, else 1 // ACCOUNTING METHOD // charge amortized cost c_hat = 3 per push. // 1 unit pays for writing the new element. // 2 units are stored as credit ON the new element itself. // invariant: every "live" element holds >=0 credit, and elements in the // second half of the array (indices n/2 .. n-1) hold exactly 2 credits. // when a resize is triggered (n == cap), the n/2 elements added since the // last resize each contribute their stored 2 credits -> 2(cap/2) = cap credits, // exactly enough to pay the copy cost of the (now) cap elements. Credit resets to 0. // POTENTIAL METHOD // Phi(D) = 2n - cap // Phi_0 = 2(0) - 1 = -1 -> shift convention or start cap=0; assume Phi_0 = 0 for simplicity // Case A (no resize): c_i = 1, Phi increases by 2 (n -> n+1, cap unchanged) // c_hat_i = 1 + 2 = 3 // Case B (resize triggers, n == cap before push): c_i = n + 1 (copy + write) // cap doubles: cap -> 2n ; n -> n+1 // Phi_before = 2n - n = n ; Phi_after = 2(n+1) - 2n = 2 // c_hat_i = (n+1) + (2 - n) = 3 // Both cases give c_hat_i = 3 = O(1), matching the accounting method exactly. ``` The two derivations above are not a coincidence: the potential function is precisely a closed-form expression for "total stored credit" in the accounting proof. This is the general pattern — a well-designed potential function is the accounting scheme's credit ledger, compressed into a single formula.
Complexity & correctness
Why the invariant proves the bound. Both methods rest on the same telescoping trick. In the accounting method, let denote total stored credit after operation , with . By construction , so
If we maintain for all (the whole content of the correctness proof), then , i.e. . This is an exact algebraic identity, not an approximation — the only "proof obligation" is the non-negativity check at every step, which must be argued for arbitrary, not just typical, operation sequences. The potential method is the identical argument with playing the role of . The requirement for every reachable is the entire correctness burden. Crucially, this must hold for every sequence of operations the adversary could choose, not just the sequence you happen to trace through by hand — the proof is over all reachable states, universally quantified.
Equivalence of the two methods. Any accounting-method proof induces a potential function: define total credit stored in under the accounting scheme. Conversely, any potential function induces an accounting scheme: treat itself as "credit currently in the bank" (not tied to specific objects, but to the aggregate state). The two frameworks have equal expressive power; the choice between them is a matter of which is easier to compute and verify for a given structure. Accounting is often more intuitive when credit has an obvious physical meaning (an unspent token per array slot); potential functions are often algebraically cleaner and easier to manipulate symbolically (e.g., for splay trees, where has no simple "per-object credit" story but is easy to bound analytically).
Complexity honesty. Amortized bounds are guarantees on the total cost of any sequence of operations, divided by . They are not:
- A worst-case bound on any single operation (a resize really does cost that one time).
- An average-case or expected bound over random inputs — amortized bounds hold for every sequence, adversarial or not, with no probability involved.
- Valid if the adversary can inspect and exploit internal state across independent data structures sharing credit — amortized bounds are for a single structure evolving through a sequence of operations on itself (though they do compose: independent structures each with amortized cost give total).
For the dynamic array, the amortized per push yields a worst-case total of for pushes starting empty — this is the real payoff: even though individual pushes can cost , no adversarial sequence of pushes can cost more than total, because doubling geometrically means total copying work is .
Worked example
We trace a binary counter supporting `INCREMENT`, incrementing a -bit binary number by flipping bits according to the ripple-carry rule (flip the lowest-order 0 bit to 1, flip every 1 bit below it to 0). The true cost of one increment is the number of bits flipped.
Potential function: , the number of 1-bits currently in the counter. Clearly always, and for the all-zero start.
Cost analysis of one increment. Suppose the increment flips trailing 1-bits to 0, then flips one 0-bit to 1 (if the counter was all 1s, it flips all bits to 0, but we track only bits that existed — standard treatment assumes no overflow, or that overflow simply resets to 0). True cost . Change in potential: decreases by (the flipped 1s) and increases by 1 (the new 1), so . Then
Every increment has amortized cost exactly , regardless of . Summing over increments starting from counter value 0 (so and ):
So increments cost total, i.e. amortized per increment — matching the intuition that low-order bits flip every time, but higher bits flip exponentially less often.
| Counter (binary) | Value | Bits flipped () | 1-bits after () | ||
|---|---|---|---|---|---|
| 000 | 0 | — | 0 | — | — |
| 001 | 1 | 1 | 1 | +1 | 2 |
| 010 | 2 | 2 | 1 | 0 | 2 |
| 011 | 3 | 1 | 2 | +1 | 2 |
| 100 | 4 | 3 | 1 | −1 | 2 |
| 101 | 5 | 1 | 2 | +1 | 2 |
| 110 | 6 | 2 | 2 | 0 | 2 |
| 111 | 7 | 1 | 3 | +1 | 2 |
Total actual cost over these 7 increments: . Total amortized cost: . The bound holds, with slack equal to , exactly matching — the telescoping identity checks out exactly, as it must by construction. Now contrast with the accounting-method version of the same problem, to show the equivalence concretely: charge amortized cost 2 per increment; deposit 1 credit on each bit as it is set to 1 (paying for the eventual cost of flipping it back to 0), and use the other 1 unit to pay for setting that bit. When a lower-order 1-bit is flipped to 0 during a later increment, spend its stored credit to cover that flip — cost already paid for. Since every bit that gets flipped to 0 was previously flipped to 1 with a deposited credit, the balance never goes negative. This is exactly the credit ledger whose total equals .
Common pitfalls & traps
| Pitfall | Why it happens | Fix |
|---|---|---|
| Treating amortized bound as worst-case-per-operation | Confusing "average over the sequence" with "every operation individually" | State explicitly: amortized bounds the sum; a single op can still spike |
| Forgetting to check for all reachable states | Only checking the states seen in one hand-traced example | Argue the invariant algebraically/structurally, over all inputs |
| Sign error: defining so it can go negative | E.g. using (backwards) instead of | Re-derive symbolically for each case and check explicitly |
| Allowing credit to go negative in the accounting method | Spending credit on an object that never actually accumulated any | Track credit per object type, not just in aggregate, unless aggregate suffices |
| Mixing up amortized with expected (probabilistic) cost | Both give "average" bounds but amortized is adversarial-sequence-safe, expected is distribution-dependent | Amortized: no randomness, holds for every sequence; expected: needs a probability model |
| Applying single-structure amortized bounds to shared/concurrent structures | Credit or potential assumes sequential, single-owner operations | Re-derive potential functions carefully under concurrency, or use different tools |
Pioneers & history
The accounting and potential methods emerged from the broader development of amortized analysis as researchers sought rigorous tools to analyze self-adjusting and dynamically resized data structures in the 1980s.
Origins · 1 minRobert Endre Tarjan and how this was discovered
Tarjan, working with Daniel Sleator, formalized the potential-method framework in the context of self-adjusting structures such as splay trees and the union-find (disjoint-set) data structure with path compression. Their analyses showed that operations dismissed as "occasionally expensive" were in fact provably cheap in total, reshaping how algorithm designers reasoned about worst-case-total versus per-operation cost. Tarjan's earlier work on graph algorithms (strongly connected components, planarity testing) had already demonstrated a taste for finding hidden invariants; the potential method generalized this instinct into a reusable proof technique across data structures.
Where this matters
Standard library dynamic arrays — C++'s `std::vector`, Java's `ArrayList`, Python's `list` — all rely on geometric (usually doubling or 1.5×) growth precisely because the potential-method argument guarantees amortized append despite occasional reallocation. Disjoint-set (union-find) structures with union-by-rank and path compression, used in Kruskal's minimum spanning tree algorithm, in compiler register-allocation interference graphs, and in image-segmentation connected-component labeling, achieve their near-linear total running time via a potential-function argument bounding the (inverse-Ackermann) amortized cost per operation. Splay trees, used in some cache and memory-management implementations for their self-adjusting locality properties, are correct and efficient only because of a potential-function proof over subtree sizes. Amortized reasoning also underlies the analysis of hash table resizing (open addressing and chaining implementations resize using the same doubling argument as dynamic arrays), incremental garbage collectors that spread work across allocations, and the competitive-ratio proofs for online caching and scheduling algorithms, which reuse the identical telescoping-potential technique. Understanding these two methods is also a direct stepping stone toward analyzing comparison-based algorithms' inherent limits, as in Comparison-Based Sorting Lower Bounds, where potential-like counting arguments (information-theoretic entropy bounds) play an analogous role in proving that no algorithm can do better than a stated bound.
Ask the tutor: Can you show how the potential method extends to prove amortized bounds for splay tree operations?
Is amortized analysis the same as average-case analysis?
No. Average-case analysis assumes a probability distribution over inputs and bounds the expected cost under that distribution; different inputs can behave very differently, and an adversarial input can still be expensive. Amortized analysis makes no probabilistic assumption at all — it bounds the total cost over any sequence of operations, including adversarially chosen ones, divided evenly across operations. A structure can have excellent amortized bounds (like the dynamic array's O(1) push) while having zero randomness anywhere in its design or analysis. The two concepts are frequently confused because both produce an "average-looking" number, but they answer fundamentally different questions: one is about typical inputs, the other is about worst-case sequences of operations on a single evolving structure.
Why bother with the potential method if the accounting method already works?
The accounting method requires you to identify a concrete, per-object credit scheme, which is intuitive for simple structures like arrays and stacks but becomes unwieldy for structures with complex shape invariants, such as splay trees or Fibonacci heaps. The potential method lets you express the same idea as a single algebraic formula — such as a sum of logarithms of subtree sizes — that can be manipulated with ordinary algebra and calculus-like reasoning rather than object-by-object bookkeeping. In practice, potential functions are often easier to state, easier to verify are non-negative, and easier to compose when analyzing complex sequences of mixed operation types. The two methods are equally powerful; potential functions are usually the more scalable notation for advanced structures.
Can the potential function be negative at some intermediate point, as long as it ends non-negative?
The requirement is for every , not just the final state. If dips below its baseline at some intermediate operation, the telescoping argument breaks for the prefix of operations up to that point, meaning you cannot guarantee the amortized bound holds for every prefix of the sequence — and since real programs can stop at any point, or query cumulative cost mid-sequence, this is a genuine correctness failure, not a cosmetic one. A valid potential function must be non-negative (relative to its baseline) after every single operation, without exception.
How do you choose a good potential function for a new data structure?
There is no fully mechanical procedure, but a reliable heuristic is: identify what makes an operation expensive, then define Φ to measure "how much stored-up structure" causes that expense, so that the expensive operation reduces Φ by roughly the amount of its cost. For a dynamic array, expensive resizes are caused by the array being "full," so Φ tracks the gap between size and capacity. For splay trees, expensive rotations are caused by imbalance, so Φ sums logarithms of subtree sizes, which decrease sharply after a splay operation restructures the tree. The general pattern: Φ should be large exactly when the structure is "primed" for an expensive operation, and that operation's cost should be absorbed by a correspondingly large drop in Φ.
Does an O(1) amortized bound guarantee real-time responsiveness, e.g. in a game engine or hard real-time system?
No, and this is an important practical caveat. Amortized bounds only guarantee the total cost is small over a long sequence; a single operation within that sequence (like the resize that copies the whole array) can still take Θ(n) time in the worst case, causing a visible latency spike. Systems with hard real-time constraints (audio processing, flight control, some game engines) often cannot tolerate even rare Θ(n) spikes and instead require genuinely worst-case-bounded structures, sometimes achieved by incremental or lazy rebuilding techniques that spread the expensive work across several subsequent cheap operations, converting an amortized bound into a true worst-case bound at the cost of extra bookkeeping.
How does amortized analysis relate to the earlier aggregate method?
The aggregate method is the crudest of the three amortized-analysis techniques: it directly bounds the total cost of operations by analyzing the whole sequence's structure at once (e.g., counting total array-copy work across all resizes), then divides by to get the amortized cost per operation. It gives the same final numeric bound as a correctly constructed accounting or potential argument, but it does not decompose the bound per operation type, making it hard to apply when a sequence mixes operations with very different natural costs (e.g., push, pop, and multipop on the same stack). The accounting and potential methods generalize the aggregate method by assigning a per-operation amortized cost, which composes cleanly across heterogeneous operation types and gives a more structural explanation of why the bound holds.
What is a common beginner error when computing Δmath Φ for a resize or restructuring operation?
The most common error is computing using the capacity or shape after the resize when the true cost of the operation should be paired with the potential drop that caused it — i.e., using inconsistent "before" and "after" snapshots, which breaks the telescoping identity. For example, in the dynamic array, one must be careful whether uses the old capacity or the new (doubled) capacity when a resize is triggered by the current push; mixing these up produces a nonsensical or even negative . The safe practice is to always compute strictly from the state before operation , and strictly from the state after, applying the operation's actual effect exactly once.