Average cost over sequence of operations. Aggregate, accounting methods.
Self-serve tutorial - low prerequisites, straightforward concepts.
Some operations are occasionally expensive, but only because they “pay back” a lot of cheap work you did earlier. Amortized analysis is the toolkit for proving that kind of performance—without assuming randomness and without pretending worst-case spikes don’t happen.
Amortized analysis bounds the average cost per operation over any sequence of operations (worst-case sequence, not probabilistic). You can prove an O(1) amortized cost even if some individual operations cost O(n), using either (1) aggregate analysis, (2) the accounting (banker’s) method, or (3) the potential method with a potential function Φ(S) over states S.
Big-O worst-case analysis answers: “How bad can one operation be?” That’s important, but it can be misleading for data structures where rare expensive operations are balanced by many cheap ones.
Classic example: dynamic arrays.
push operations are O(1).Worst-case per operation is O(n), but in practice we feel “constant-ish” time. Amortized analysis is the formal proof of that feeling.
Amortized analysis is not average-case probability. There is no distribution over inputs.
Instead we fix any sequence of m operations (even an adversarially chosen one), and we show:
∑ (actual cost of op i) ≤ (amortized cost bound) · m + constant
Then the amortized cost per operation is the average upper bound over that sequence.
Formally, if operation i has actual cost cᵢ, we want a bound like:
(1/m) ∑ᵢ cᵢ ≤ O(f(n))
for all sequences of length m, where n is a relevant size parameter (often current number of elements).
Amortized analysis usually comes in three equivalent “voices”:
| Method | Core move | What you track | When it feels natural |
|---|---|---|---|
| Aggregate | Bound total cost of m ops directly | Total work across the whole run | Resizing arrays, periodic rebuilds |
| Accounting (banker’s) | Overcharge some ops, store “credits” | Credits attached to items/structure | Stack with multipop, splay-ish intuitions |
| Potential | Define Φ(S) on state S; amortized = actual + ΔΦ | A single numeric potential | Systematic, composable proofs |
You’ll often start with aggregate to get intuition, then write a clean potential function to make the proof reusable.
Two ingredients:
That stored value can be literal credits (accounting) or a potential Φ(S) (potential method).
A potential function maps a state S of the data structure to a real number:
Φ(S) ∈ ℝ
Intuition: Φ(S) is “stored credit” or “energy.” If Φ increases, we’re saving credit for later. If Φ decreases, we’re spending credit to subsidize a costly operation.
A standard requirement is:
Then we define amortized cost:
ĉᵢ = cᵢ + (Φ(Sᵢ) − Φ(Sᵢ₋₁))
and show that ĉᵢ ≤ some bound for every operation.
Finally, the magic telescopes:
∑ᵢ ĉᵢ = ∑ᵢ cᵢ + (Φ(S_m) − Φ(S₀))
So if Φ(S_m) ≥ 0 and Φ(S₀) ≥ 0, then:
∑ᵢ cᵢ ≤ ∑ᵢ ĉᵢ + Φ(S₀)
Meaning: bounding amortized costs bounds total actual cost.
This is the formal reason amortized analysis is worst-case over sequences: the inequality holds for every sequence, not “on average.”
Aggregate analysis is the quickest path to intuition: instead of bounding each operation, you bound the total work across a whole sequence, then divide by m.
This is perfect when expensive events are structured and countable (like “how many resizes can happen?”).
Suppose we have a structure that is cheap most of the time, but sometimes does a rebuild costing proportional to its size.
To use aggregate analysis:
Consider a dynamic array that doubles capacity when full.
push(x) writes x into the next slot: cost 1.Let’s count element copies over m pushes starting from empty.
Capacities go 1, 2, 4, 8, …
When capacity is k and we grow, we copy k elements.
So total copy cost over all grows up to size m is:
1 + 2 + 4 + … + ⌊m/2⌋ < 2m
because geometric series:
∑_{j=0}^{t} 2ʲ = 2^{t+1} − 1
Thus total actual cost over m pushes is:
So total < 3m, hence amortized cost is < 3 = O(1).
Aggregate analysis shows:
(1/m) ∑ᵢ cᵢ ≤ constant
But it doesn’t produce a per-operation “bank balance” that composes well with other operations. If you later add pop, shrink, or other behavior, aggregate analysis can become messy.
That’s why we often upgrade to accounting or potential methods: they track stored credit in a way that’s easier to extend.
A surprisingly common amortized trick is:
Then total steps over m operations is O(m).
This is the philosophical bridge to the next methods: you’re already doing “charging,” just informally.
When you can identify a monotone progress measure (size, capacity, number of items that can be moved), aggregate bounds often become a one-liner:
Total cost ≤ O(total progress)
But when progress can go up and down, you’ll want explicit stored credit (accounting) or explicit Φ(S) (potential).
Aggregate analysis is great when behavior is monotone. But many data structures have operations that undo each other (push vs pop) or have multiple interacting costs.
Accounting and potential methods let you say:
The result is a uniform amortized bound even if the sequence alternates adversarially.
You assign each operation i an amortized charge ĉᵢ.
If the bank never goes negative, then the total amortized charge upper-bounds total actual cost:
∑ᵢ cᵢ ≤ ∑ᵢ ĉᵢ
Intuition: if you always have enough saved credit to pay for extra work, you can’t “cheat.”
Operations on a stack:
push(x): cost 1pop(): cost 1 (if nonempty)multipop(k): pop up to k items; cost = number poppedWorst-case multipop(k) is O(n). But amortized, it’s O(1).
Accounting proof idea:
push an amortized cost of 2.When an item is popped (by pop or by multipop), spend its stored credit to pay the pop.
Each item is popped at most once, so the credits always suffice.
Thus each operation has amortized O(1).
Accounting attaches credits to “places” (items, nodes, edges). That’s intuitive, but can be ad hoc.
The potential method compresses the entire credit state into a single function:
Φ(S): state → ℝ
Then define amortized cost:
ĉᵢ = cᵢ + (Φ(Sᵢ) − Φ(Sᵢ₋₁))
If you can prove ĉᵢ ≤ K for every operation (for some constant K), then:
∑ᵢ cᵢ
= ∑ᵢ (ĉᵢ − (Φ(Sᵢ) − Φ(Sᵢ₋₁)))
= ∑ᵢ ĉᵢ − (Φ(S_m) − Φ(S₀))
≤ ∑ᵢ ĉᵢ + Φ(S₀)
And if Φ(S₀) = 0 and Φ(S_m) ≥ 0, then:
∑ᵢ cᵢ ≤ ∑ᵢ ĉᵢ
So bounding amortized costs bounds actual total cost.
A good Φ(S) should:
Common patterns:
| Situation | Potential often depends on |
|---|---|
| Resizable arrays | difference between capacity and size |
| Balanced rebuilding | distance from “ideal balance” |
| Union-Find (later) | ranks / sizes / structural complexity |
| Splay trees | weighted path lengths (advanced) |
If accounting stores credits on items, potential is often:
Φ(S) = total stored credits in the structure
So potential can be viewed as “the banker’s balance,” but expressed as a function of state rather than as an explicit ledger.
Potential functions are typically chosen to be nonnegative on all reachable states:
∀S reachable: Φ(S) ≥ 0
This ensures the telescoping argument yields an upper bound on actual cost.
Sometimes Φ(S) can be negative if you can still bound Φ(S_m) − Φ(S₀), but for most learning-level proofs, keep Φ ≥ 0 to avoid subtle pitfalls.
You’ll often see potential written as Φ, and state as S.
We’ll use:
Then:
ĉᵢ = cᵢ + ΔΦᵢ
Think: amortized = actual + change in stored credit.
If ΔΦᵢ is positive, you’re “saving” credit (amortized > actual).
If ΔΦᵢ is negative, you’re “spending” credit (amortized < actual).
Amortized analysis shows up whenever a data structure occasionally does heavy maintenance:
The point is not that worst-case spikes disappear—they don’t. The point is that spikes are paid for by many cheap operations.
When you design an algorithm, amortized thinking helps you decide:
If you pick the right invariant, you can often get:
Union-Find with union by rank/size and path compression has famously fast operations: “near constant.”
That result is typically stated as:
This is an amortized statement: single operations can be more expensive, but across the whole sequence the average is tiny.
To understand that proof later, you need comfort with:
This node is the prerequisite mindset.
Here’s a pragmatic guide:
| If you see… | Try… |
|---|---|
| A simple growth pattern (doubling, geometric) | Aggregate analysis first |
| A while-loop that deletes items (each item removed once) | Accounting (“each item prepaid”) |
| Multiple interacting operations and non-monotone state | Potential function Φ(S) |
Amortized analysis is a guarantee about any sequence:
It’s deterministic: an adversary can pick the worst possible sequence, and the total cost bound still holds.
That’s why it’s so valuable for core data structures used everywhere.
Once you can do that reliably, you’re ready to read and trust “near constant amortized time” claims in advanced structures like Union-Find.
A dynamic array supports push(x). If the array has size n and capacity cap:
Assume starting from empty with cap = 1.
Goal: prove amortized O(1) per push.
Aggregate view (total copies):
Let m be the number of pushes.
Resizes occur at sizes 1, 2, 4, 8, …
When resizing from capacity k to 2k, we copy k elements.
Total copies ≤ 1 + 2 + 4 + … + ⌊m/2⌋ < 2m.
Total writes = m.
So total cost < 3m ⇒ amortized cost < 3 = O(1).
Potential view (define Φ):
Let state S be (n, cap).
Choose Φ(S) = 2n − cap.
Check nonnegativity on reachable states:
Because cap is always a power of 2 and n ≤ cap, we have 2n − cap ≥ 0 only when n ≥ cap/2.
So Φ is not always ≥ 0.
Fix by clamping: Φ(S) = max(0, 2n − cap).
This keeps Φ(S) ≥ 0 for all states.
Amortized cost when no resize (n < cap):
Actual cost c = 1.
If n < cap/2 then Φ = 0 before and after push until you cross cap/2.
So ΔΦ = 0 and ĉ = 1.
If n ≥ cap/2 then Φ = 2n − cap.
After push, n increases by 1 so Φ increases by 2.
Thus ΔΦ = 2 and ĉ = 1 + 2 = 3.
Amortized cost when resize happens (n = cap):
Before: n = cap so Φ_before = max(0, 2cap − cap) = cap.
Actual cost: copy cap items (cap) + write (1) ⇒ c = cap + 1.
After resize: new cap' = 2cap, new n' = cap + 1.
Φ_after = max(0, 2(cap + 1) − 2cap) = max(0, 2) = 2.
So ΔΦ = 2 − cap.
Amortized cost:
ĉ = c + ΔΦ = (cap + 1) + (2 − cap) = 3.
Conclusion:
Every push has amortized cost ≤ 3, so any sequence of m pushes costs ≤ 3m + Φ(S₀).
With empty start Φ(S₀) = 0, total is O(m) ⇒ O(1) amortized per push.
Insight: The resize operation is expensive (cap copies), but the potential drops by about cap at the same moment, “paying for” the copies. Choosing Φ(S) so that it rises during cheap pushes and falls during a resize is the whole art.
Operations on a stack:
Goal: prove O(1) amortized time per operation for any sequence.
Define the accounting scheme:
Charge each push an amortized cost ĉ = 2.
Charge each pop and multipop an amortized cost ĉ = 0.
Interpretation:
Show credits never go negative:
Each item receives 1 stored credit when it is pushed.
An item can be removed (popped) at most once across the entire sequence.
Whenever pop/multipop removes an item, it spends that item’s stored credit to pay its actual cost 1.
Thus every actual pop is paid by a credit that definitely exists.
So the bank balance (total stored credits) is never negative.
Bound total actual cost by total amortized charge:
Let there be P pushes in the sequence.
Total amortized charge is ∑ ĉ = 2P.
Total actual cost is:
So total actual ≤ 2P = total amortized.
Convert to per-operation bound:
If the total number of operations is m, then P ≤ m.
Total actual cost ≤ 2m ⇒ amortized O(1) per operation.
Insight: The expensive-looking operation is multipop(k), but its work is capped by a lifetime rule: each pushed item can only be popped once. Accounting makes that rule explicit by attaching a prepaid coin to each item.
A binary counter supports increment(): add 1 to a k-bit binary number.
Cost model: flipping one bit costs 1.
In the worst case (e.g., 111…111 + 1), increment flips k bits ⇒ O(k).
Goal: show amortized O(1) cost per increment over any sequence.
Observe what happens in increment:
So actual cost c = t + 1.
Define potential Φ(S):
Let S be the current bitstring.
Let Φ(S) = (# of 1 bits in S).
Clearly Φ(S) ≥ 0 always.
Compute ΔΦ for one increment:
Trailing t ones become zeros: decreases Φ by t.
One zero becomes one: increases Φ by 1.
So ΔΦ = 1 − t.
Compute amortized cost:
ĉ = c + ΔΦ
= (t + 1) + (1 − t)
= 2.
Conclude:
Every increment has amortized cost 2, so any sequence of m increments performs at most 2m bit flips in total (up to an additive constant from Φ(S₀)).
Insight: A long carry chain is expensive exactly when you erase many 1s—so the potential (number of 1s) drops sharply and reimburses the work.
Amortized analysis bounds average cost per operation over any sequence, not an expected value over random inputs.
To prove amortized bounds, you either bound total cost directly (aggregate) or shift cost across operations (accounting/potential).
Accounting method: overcharge some operations and store credits; the invariant is that stored credits never go negative.
Potential method: choose Φ(S) ≥ 0 and define ĉᵢ = cᵢ + (Φ(Sᵢ) − Φ(Sᵢ₋₁)); telescoping sums convert amortized bounds to total cost bounds.
A good Φ(S) increases during cheap operations (saving credit) and decreases during expensive ones (spending credit).
Many “worst-case O(n)” operations (resize, multipop, carries) are O(1) amortized because expensive work can happen only when enough credit has accumulated.
Amortized reasoning is foundational for advanced data structures, including Union-Find’s near-constant time guarantees.
Confusing amortized analysis with average-case (probabilistic) analysis; amortized is deterministic over worst-case sequences.
Picking a potential function Φ(S) that can go negative without carefully bounding Φ(S_m) − Φ(S₀), causing the telescoping argument to fail.
Charging credits in the accounting method without proving the bank balance never goes negative (i.e., silently borrowing from the future).
Bounding each operation’s amortized cost but forgetting to state the cost model precisely (what counts as 1 unit of work).
Dynamic array with growth factor 3/2: capacity multiplies by 3/2 (round up) when full. Show that push(x) is still O(1) amortized. Give an aggregate analysis bound on total copies over m pushes.
Hint: Sum the capacities copied at each resize as a geometric series: k + (3/2)k + (3/2)²k + … up to O(m). The ratio is > 1 but constant.
Let capacities be c₀, c₁, c₂, … where c_{j+1} = ⌈(3/2) c_j⌉. When resizing from c_j to c_{j+1}, you copy c_j elements.
After enough pushes to reach size m, the largest capacity is O(m).
Total copies = ∑_{j} c_j.
Since c_j grows geometrically with ratio about 3/2, the sum of all previous capacities is dominated by the last:
∑_{j=0}^{t} c_j ≤ c_t · (1 + 2/3 + (2/3)² + …) = 3c_t.
With c_t = O(m), total copies = O(m).
Adding m writes gives total cost O(m), hence amortized O(1) per push.
Binary counter variant: define cost as the number of bits inspected (read) during increment, not just flipped. Suppose your increment scans trailing 1s until it finds a 0, then flips. Show the amortized cost is still O(1) using a potential function.
Hint: Use the same Φ(S) = number of 1s. Relate “inspected bits” to trailing 1s + the first 0. The scan length is t + 1.
Let t be the number of trailing 1s. The algorithm inspects t trailing 1 bits plus the first 0 bit, so actual cost c = t + 1 (inspections). It may also flip bits, but if flips are included they are also O(t + 1).
Choose Φ(S) = (# of 1 bits). Then ΔΦ = 1 − t as in the standard proof.
Amortized cost:
ĉ = c + ΔΦ = (t + 1) + (1 − t) = 2.
Thus amortized O(1) per increment for inspections (and also for flips if counted similarly).
A stack supports push and pop, and also an operation clear() that removes all items currently in the stack. Actual cost of clear() is the number of removed items. Prove O(1) amortized per operation with an accounting argument.
Hint: Same idea as multipop: each pushed item can be removed at most once, whether by pop or clear.
Charge each push amortized cost 2: pay 1 for the actual push and store 1 credit on the item. Charge pop and clear amortized cost 0. When pop removes an item, spend its stored credit to pay cost 1. When clear removes r items, spend each item’s stored credit to pay its unit removal cost; total credits spent = r. Since each item is removed at most once, credits never go negative. Therefore total actual cost is at most total amortized charge, which is 2·(#pushes) ≤ 2m for m operations, giving O(1) amortized.
Unlocks: Disjoint Sets
Related nodes you may want next: