Amortized Analysis
📖 Concept
Amortized analysis looks at the average cost per operation over a sequence of operations, rather than the worst case of any single operation. Some operations are expensive occasionally but cheap most of the time.
Why it matters: Without amortized analysis, you'd think Array.push() is inefficient (because it sometimes needs to resize). In reality, it's O(1) amortized — the occasional O(n) resize is spread across all the cheap O(1) pushes.
Key examples:
1. Dynamic Array (JavaScript Array / ArrayList):
- Push is usually O(1) — just add to the end
- When full, the array doubles in size — this single push is O(n) (copy all elements)
- But doubling happens rarely: after 1, 2, 4, 8, 16... pushes
- Total cost for n pushes: n + n/2 + n/4 + ... ≈ 2n → O(1) amortized per push
2. Hash Table Rehashing:
- Insert is usually O(1)
- When load factor exceeds threshold, rehash all elements — O(n)
- Happens rarely enough that amortized cost is still O(1)
3. Stack with multipop:
- pop() is O(1), but multipop(k) removes k elements — O(k)
- Over n operations total, each element is pushed and popped at most once
- Amortized: O(1) per operation
Methods of amortized analysis:
- Aggregate method: Total cost / number of operations
- Accounting method: Assign "credits" to cheap operations to pay for expensive ones
- Potential method: Define a potential function that captures saved-up work
🏠 Real-world analogy: Car insurance. Most months you pay a small premium (O(1)). One accident costs a lot (O(n)). But spread over years, the average monthly cost is still manageable — that's amortized cost.
💻 Code Example
1// DYNAMIC ARRAY — Why push() is O(1) amortized2class DynamicArray {3 constructor() {4 this.data = new Array(2); // Start with capacity 25 this.length = 0;6 this.capacity = 2;7 }89 push(value) {10 if (this.length === this.capacity) {11 this._resize(); // O(n) — but happens rarely!12 }13 this.data[this.length] = value; // O(1) — happens every time14 this.length++;15 }1617 _resize() {18 this.capacity *= 2; // Double the capacity19 const newData = new Array(this.capacity);20 for (let i = 0; i < this.length; i++) {21 newData[i] = this.data[i]; // Copy all elements — O(n)22 }23 this.data = newData;24 console.log(`Resized to ${this.capacity}`);25 }26}2728// Demonstration: push 16 elements29const arr = new DynamicArray();30for (let i = 0; i < 16; i++) {31 arr.push(i);32}33// Resizes at: 2→4 (copy 2), 4→8 (copy 4), 8→16 (copy 8)34// Total copies: 2 + 4 + 8 = 14 for 16 pushes35// Amortized: ~1 copy per push → O(1)3637// TypeScript version38class DynamicArrayTS<T> {39 private data: (T | undefined)[];40 private _length: number = 0;41 private capacity: number;4243 constructor(initialCapacity: number = 2) {44 this.capacity = initialCapacity;45 this.data = new Array(this.capacity);46 }4748 push(value: T): void {49 if (this._length === this.capacity) {50 this.resize();51 }52 this.data[this._length] = value;53 this._length++;54 }5556 private resize(): void {57 this.capacity *= 2;58 const newData: (T | undefined)[] = new Array(this.capacity);59 for (let i = 0; i < this._length; i++) {60 newData[i] = this.data[i];61 }62 this.data = newData;63 }6465 get length(): number { return this._length; }66}6768// ACCOUNTING METHOD INTUITION69// Each push "pays" 3 coins:70// 1 coin: for the push itself71// 2 coins: saved for future resize72// When resize happens (copy n elements),73// the n saved coins pay for it exactly.74// Result: each push costs 3 → O(1) amortized
🏋️ Practice Exercise
Practice Problems:
- If a dynamic array starts at size 1 and doubles, how many total copies after 32 pushes?
- What if the array triples instead of doubles? Still O(1) amortized?
- Why is it BAD to grow by a constant (e.g., add 10 slots each time)?
- Explain why JavaScript's
Array.push()is O(1) amortized - A stack supports push, pop, and multipop(k). Prove multipop is O(1) amortized
- Hash table with load factor 0.75 resizes by 2x. What's amortized insert cost?
- What would happen to amortized cost if we shrink the array when it's only 1/4 full?
- Compare: ArrayList.add() in Java vs linked list append — amortized costs
- Why doesn't pop() on a dynamic array ever need to resize?
- Design a queue using two stacks — what's the amortized dequeue time?
⚠️ Common Mistakes
Confusing amortized with average case — amortized is a GUARANTEE over any sequence, not a probabilistic average
Thinking O(1) amortized means every operation is O(1) — some individual operations CAN be O(n), but they're rare enough
Not understanding the doubling strategy — growing by a fixed amount (e.g., +10) gives O(n) amortized, not O(1)
Applying amortized analysis to single operations — it only makes sense over a SEQUENCE of operations
Forgetting that amortized O(1) doesn't help with real-time systems — a single O(n) resize can cause a latency spike
💼 Interview Questions
🎤 Mock Interview
Practice a live interview for Amortized Analysis