Amortized Analysis
Some operations are occasionally expensive but cheap on average across any sequence. Amortized analysis bounds the average cost per operation over a worst-case sequence — not an expectation — so a rare costly step is paid for by the many cheap ones around it.
╌╌╌╌
The asymptotic tools from the previous lessons bound a single operation in the worst case. But for many data structures that bound is misleading. Appending to a dynamic array is usually (write into the next free slot), yet once in a while the array is full and the append must copy every element to a larger block, costing . Bounding each append by its worst case, , and multiplying by appends gives , which hugely overstates the truth: appends really take total. The expensive copies are rare, and the cheap appends between them more than pay for them.
Amortized analysis is the technique for making that intuition rigorous. It charges each operation an amortized cost so that the total over any sequence is correct, while individual amortized costs are smooth and easy to reason about.
What amortized
means — and does not
Fix a data structure and consider a sequence of operations performed on it.
The point of the definition is that the amortized costs need only upper-bound the actual total; we are free to choose smooth that overcharge cheap operations and undercharge expensive ones, as long as the running sum never falls behind. If every , then operations cost at most in total, no matter how the costs are distributed inside the sequence.
The distinction matters in both directions. An average-case bound can be excellent on random inputs and useless against the one input your program actually sees; an amortized bound cannot. Conversely, an amortized bound says nothing about any single operation, only about prefixes of the sequence: the -th append may well cost , and the guarantee only promises that the operations before it were cheap enough to compensate. We return to when that trade is unacceptable at the end.
The three classical methods (aggregate, accounting, and potential) all certify the same kind of bound; they differ only in bookkeeping.1 Each is developed below, first on one running example so the methods can be compared side by side, then on a second structure so the mechanics show twice.
The running example: dynamic-array doubling
A table holds items in a block of slots. writes into the next free slot; but when the block is full (), it first doubles the block, allocating slots and copying all existing items over, then inserts.
- 1if then
- 2allocate with slot;
- 3if thenblock full: grow
- 4allocate with slots
- 5copy all items intothe expensive step
- 6;
- 7insert into ;
- 8return
Count the cost of an insert as for the write plus, when it doubles, the number of items copied. Inserts that do not trigger a doubling cost . The -th insert triggers a doubling exactly when is a power of , copying items, so its cost is . Plotting cost against operation index shows the characteristic picture: a flat baseline of , punctuated by spikes at that double in height.
The naive analysis multiplies the worst single insert, , by inserts and concludes . The truth is total, amortized per insert, and each of the three methods proves it in its own vocabulary. Watch how the same fact, that cheap inserts outnumber and prepay the copies, gets encoded three different ways.
Method 1: aggregate analysis
The aggregate method is the most direct: bound the total cost of any sequence of operations as a whole, then divide by . Every operation is assigned the same amortized cost, , where is the worst-case total.
The dynamic array: sum the sequence
For inserts into an initially empty table, separate the two kinds of work. Every insert performs exactly one write, contributing in total. The copies happen only at doublings: the insert at index copies items, and doublings occur for every with . The total number of items copied is therefore a geometric sum:
Adding the writes,
so the amortized cost per insert is . Concretely, for : the writes cost , the doublings copy items, and the total is . The spikes in the figure above are tall, but their combined area is smaller than the baseline they interrupt.
The whole argument is one sum. That is the aggregate method's appeal: when the total can be computed directly, nothing more is needed.
A stack with multipop
The second aggregate example needs a global counting argument rather than a closed-form sum. Augment the usual stack ( and , each ) with one more operation, , which pops the top items, where is the current size.
- 1while not and do
- 2calldiscard one item
- 3
- 4return
A single can be expensive: on a stack of items, runs the loop times, costing . With as large as , the naive per-operation bound is , suggesting for the whole sequence.
That bound is far too pessimistic. One fact tightens it: each item is popped at most once for each time it is pushed. Over a sequence of operations there are at most pushes, so the total number of pop actions, whether by or inside , is at most . Every operation does work besides popping, contributing another . Hence the total cost of any sequence of operations is .
Aggregate analysis is appealing when a clean global count (here, pops pushes
) bounds the total directly. Its limitation is that it assigns one cost
to all operation types; the next two methods let us charge them differently.
Method 2: the accounting method
The accounting method assigns each operation type its own amortized cost, called its charge, which may differ from its actual cost. When an operation's charge exceeds its actual cost, the surplus is stored as credit on specific elements of the data structure; when an operation costs more than its charge, it spends stored credit to cover the difference. Erickson develops the same idea as taxation: overtax the cheap operations and let the treasury pay for the expensive ones.2
The one rule that makes this a valid proof:
If credit stays non-negative, the amortized charges upper-bound the actual costs over every prefix, matching the definition, so the per-operation charges are a valid amortized bound.
The dynamic array: charge three dollars per insert
The aggregate bound of per insert suggests the scheme: charge every exactly units. Spend them as follows.
- unit pays for writing the new item into its slot.
- unit is banked on the new item itself, reserved for the next time a doubling copies it.
- unit is banked on the new item on behalf of one older item — one that was already copied by the last doubling and has no credit left.
Why the third unit works out: right after a doubling to capacity , the table holds exactly items, all with zero credit (the doubling spent it). Before the next doubling can fire, another inserts must arrive. Each new item banks units, one for its own future copy and one for exactly one credit-less older item; the new items cover the older items one-for-one.
When the table fills at , the newcomers hold units — exactly the cost of copying all items into the new block. The doubling spends every credit, the copied items land in the new block with no credit, and the invariant is re-established with the table again half full. No operation ever overdraws, so the charge of is a valid amortized cost.
The scheme also explains the constant. Two units would not suffice: a new item could pay for its own future copy but not for the older item copied in the same doubling. Charging keeps the credit non-negative.
The binary counter
The second accounting example. A -bit binary counter starts at and supports , which adds . The cost of an increment is the number of bits it flips.
- 1
- 2while and do
- 3flip a trailing 1 down to 0 (carry)
- 4
- 5if then
- 6set the first 0 bit
- 7return
A single increment can flip many bits: incrementing to flips four. In the worst case (, then a carry out) an increment costs , so increments appear to cost .
The truth is , and the accounting method shows it cleanly. Charge each increment units, and store unit of credit on every bit that is set to . When a bit flips , pay unit for the flip and bank unit on that bit. When a bit flips inside the carry loop, pay for the flip with the credit already sitting on that bit — for free, from the increment's perspective.
Each sets exactly one bit to (the bit at the end), so it banks exactly one new credit and spends units total: to flip that bit up, to bank. Every flip in the carry chain is already paid for. Credit equals the number of -bits in the counter, which is never negative, so the invariant holds.
The aggregate method reaches the same constant from a different direction, and the cross-check is worth seeing. Bit flips on every increment; bit on every second; in general bit flips only when the increment count crosses a multiple of , so over increments it flips times. The total number of flips is
amortized cost less than per increment — the same the accounting scheme charges. Half of all the work happens in bit , a quarter in bit , and the tail vanishes geometrically.
The accounting method's strength is its locality: credit lives on concrete elements (here, the -bits), and you verify the bound by checking that whoever pays an expensive operation's cost has the credit on hand.
Method 3: the potential method
The potential method is the most flexible and the one used most in practice. Instead of tracking credit on individual elements, it assigns the entire data structure a single number, the potential , that measures stored-up work. An operation's amortized cost is its actual cost plus the change in potential it causes.
Why this works: the potential changes telescope. Summing over the sequence,
Since , the trailing term is non-negative, so — precisely the amortized-cost definition. An expensive operation (large ) is affordable only if it drops the potential enough to offset itself; a cheap operation that builds potential pre-pays for the expensive one to come.
The dynamic array, one more time
Aggregate summed the sequence and accounting placed coins on items; the potential
method compresses both into one function, and the same idea will reappear for
deletion and for resizing hash tables. Choose the potential to measure how close the table is to overflowing
:
Right after a doubling the table is half full ( before the pending insert lands), so is small; just before the next doubling it is completely full (), so — exactly enough banked potential to pay for copying all items. The potential is never negative because the table is always at least half full once it has grown. Plotted over a run of inserts, sawtooths: up by per cheap insert, falling back down whenever a doubling spends it.
The doubling expense is invisible in the amortized cost: a doubling insert costs the same flat as a trivial one, because the potential it consumes ( drops from back to ) matches the potential the cheap inserts before it built up. That is the whole mechanism, and it is why the flat dashed line in the cost figure sits at no matter how tall the spikes.
The binary counter, one more time
The counter's accounting scheme kept a coin on every -bit; the corresponding potential is simply the coin count:
Suppose the -th increment resets trailing s to and then sets one bit to . Its actual cost is , and the number of -bits changes by . The amortized cost is
for every increment, no matter how long the carry chain: a long chain has a large and an equally large potential drop, and the two cancel except for the constant. (If the counter overflows — all bits are and the increment clears them all — then , the potential drops by , and the amortized cost is .) Since and , the telescoped total is : the same bound as before, now with no coins to track.
Choosing a method
All three methods prove the same bound; the choice is one of convenience.
Aggregate and accounting can be seen as special cases of the potential method,
with the potential playing the role of remaining budget
and total stored credit
respectively, so when in doubt, reach for a potential function. The array and counter examples
above show the translation: the counter's (count of -bits) is its
total accounting credit, and the array's
equals the credits held by items inserted since the last doubling.
When an amortized bound is not enough
An amortized insert still permits a single insert that costs .
For throughput (total work over the whole sequence) that is irrelevant. For
latency it can be fatal: an audio callback, a game frame, a real-time control
loop, or a packet-processing path that stalls for one copy misses its
deadline, and the average is irrelevant to that one pause. Amortized analysis
answers how much work in total
, not how long is the longest pause
.
When the pause matters, the standard remedy is to de-amortize: keep both the old and the new block during growth and move a constant number of items on every subsequent insert, so the copy finishes before the new block itself fills. Every operation then costs in the worst case, at the price of extra space and constant-factor overhead. Real-time and latency-sensitive systems pay that price; everything else takes the simpler amortized structure.
Where amortized analysis recurs
This machinery recurs throughout the course. It is the only accurate way to state the running time of several data structures you will meet later:
- Union–Find. With union by rank and path compression, a sequence of operations runs in amortized time, where is the inverse Ackermann function — effectively constant. The proof is a sophisticated potential argument.
- Hash tables. A hash table that doubles (and halves) its bucket array to keep the load factor bounded uses exactly the argument above, giving amortized insert and delete.
- Dynamic arrays (
vector,ArrayList, Pythonlist) are the doubling table itself; their amortized append is what makes them the default sequence container.3
The recurring moral: when worst-case-per-operation overcounts because expensive steps are rare and self-limiting, amortize.
Persistence and competitive analysis
Two threads extend the machinery here. The first is persistent and functional data structures. The accounting and potential methods assume the structure is used linearly — each version replaces the last — so banked credit is spent at most once. When an old version can be reused (as in a purely functional setting, where nothing is mutated in place), a naive amortized bound breaks, because an adversary can repeatedly force the one expensive operation the credit was saved for. Okasaki's work resolves this with lazy evaluation and scheduling: memoized thunks make the expensive step happen only once even under reuse, restoring the amortized bound and yielding functional queues and deques with the same amortized costs as their imperative cousins.4 His names for the accounting and potential methods — the banker's and physicist's methods — are now standard.
The second thread is competitive analysis, which applies the same average-over-a-sequence idea to online algorithms that must respond to each request before seeing the next. The self-adjusting splay tree of Sleator and Tarjan achieves amortized cost per operation through a potential argument almost identical to the ones above, and their move-to-front and paging results launched the study of how well an online algorithm can do against an adversary that knows the future.5 Amortized analysis, in short, is the entry point to a much larger theory of sequences of operations rather than single ones.
Takeaways
- Amortized cost is the worst-case average over a sequence: assign with for every sequence. It is not expected or average-case — there is no probability anywhere, and the bound survives an adversarial input.
- Aggregate: bound the whole sequence's cost, divide by . For the array, writes plus copies give a total under ; multipop is amortized because total pops total pushes; the counter flips bits.
- Accounting: overcharge cheap operations, bank the surplus as credit on elements, spend it on expensive ones; keep credit . Charge per append (write, own future copy, one older item's copy) or per increment (flip a bit up, bank a coin on it).
- Potential: encode banked work as ; then , and the changes telescope. Doubling arrays: , amortized . The counter: number of -bits, amortized .
- Geometric growth is essential: doubling makes the copy cost telescope to a constant; growing by a fixed increment gives amortized.
- An amortized bound is a throughput guarantee, not a latency guarantee: individual operations may still stall for . Real-time systems de-amortize (incremental copying) to get worst-case at a constant-factor price.
- The technique underpins union–find, resizable hash tables, and every dynamic array.
Footnotes
- CLRS, Introduction to Algorithms, Ch. 16 — Amortized Analysis: the aggregate, accounting, and potential methods, with the multipop stack, binary counter, and dynamic table as the chapter's running examples. ↩
- Erickson, Algorithms — the amortized-analysis chapter frames the accounting method as taxation and derives the potential method from it; the binary counter and multipop stack appear there in the same roles. ↩
- Skiena, The Algorithm Design Manual, §3.4 — Dynamic Arrays: the doubling construction and its amortized append. ↩
- Okasaki, C. (1998). Purely Functional Data Structures. Cambridge University Press — lazy evaluation and scheduling to make amortized bounds hold under persistence; the banker's and physicist's methods. ↩
- Sleator, D. D. & Tarjan, R. E. (1985).
Self-adjusting binary search trees.
Journal of the ACM 32(3) — splay trees and the potential argument for amortized operations; Sleator & Tarjan (1985),Amortized efficiency of list update and paging rules,
CACM 28(2), on competitive analysis. ↩
╌╌ END ╌╌