Latticework

Command Palette

Search for a command to run...

Complexity Analysis

Amortized Analysis

18 min

Explanation

Python's list.append() is described as O(1) — but that can't be literally true for EVERY call, since occasionally the underlying array runs out of room and has to be resized, which means copying every existing element into a bigger array (an O(n) operation). Amortized analysis resolves this apparent contradiction: individual operations can occasionally be expensive, but the AVERAGE cost per operation, over a long sequence, is still O(1).

def total_resizes(n):
    capacity = 1
    size = 0
    resizes = 0
    for _ in range(n):
        if size == capacity:
            capacity *= 2
            resizes += 1
        size += 1
    return resizes

print(total_resizes(1000))   # far fewer than 1000 -- doubling grows capacity exponentially
Try it

Resizes grow logarithmically (roughly doubling n only adds ONE more resize) -- 1,000,000 appends trigger only about 20 resizes total, not 1,000,000.

Loading editor…
Explanation

Here's the key insight: because capacity DOUBLES each time (not just increases by a fixed amount), each resize copies roughly as many elements as ALL the previous resizes combined copied in total. Summing 1 + 2 + 4 + 8 + ... + n/2 (the sizes at each resize) totals just under 2n — proportional to n, not to the NUMBER of resizes.

def total_copy_operations(n):
    capacity = 1
    size = 0
    total_copies = 0
    for _ in range(n):
        if size == capacity:
            total_copies += capacity   # this resize's copying cost
            capacity *= 2
        size += 1
    return total_copies

Since the total copying work across n appends is O(n), the AVERAGE (amortized) cost per individual append is O(n)/n = O(1) — even though any single append might trigger an O(n) resize. This is why doubling (not adding a fixed amount) is the standard growth strategy for dynamic arrays — growing by a fixed amount instead would make resizes happen proportionally to n, making the amortized cost O(n), not O(1).

Exercise

Write `total_resizes(n)`: simulate a dynamic array starting at capacity 1 that DOUBLES its capacity whenever a new element wouldn't fit, as you append `n` elements one at a time. Return the total number of RESIZE operations (not appends).

Exercise

Write `total_copy_operations(n)`: same simulation, but return the TOTAL number of element copies performed across every resize (each resize copies every existing element — `capacity` of them — into the new array).

Quiz

Why is the amortized cost of appending to a dynamic array O(1), even though an individual resize is O(n)?

Checkpoint

You understand why doubling a dynamic array's capacity keeps appends amortized O(1), even though individual resizes are O(n).