Amortized and Space Complexity

5 min read

Reading Progress0%
Dsa Index
Dsa Index

Amortized and Space Complexity

1. What It Is and Why It Exists

Amortized complexity is the average cost per operation across a worst-case sequence of operations, when an occasional expensive operation is paid for by many cheap ones. It exists because reporting only the single worst operation overstates the real cost. The canonical case is Python's list.append: most appends are O(1), but when the backing array is full it allocates a bigger array and copies everything (O(n)). If you doubled the array, that O(n) copy happens only every ~n appends, so the cost averaged over the sequence is O(1) amortized — the expensive resize is "amortized away" by the cheap appends that follow.

Space complexity measures the extra memory an algorithm uses as a function of input size. The number interviewers want is auxiliary space — memory you allocate beyond the input itself — because the input is a sunk cost the caller already paid. The non-obvious part candidates miss: the call is space. A that goes n deep is O(n) space even if it allocates no array, and that is exactly the kind of thing an interviewer pushes on.

QUICK CHECK

A backend service uses a dynamic array (like Python's list) to buffer incoming log entries. Most append operations run in O(1), but occasionally the array must resize, copying all existing entries — an O(n) operation. How should the overall cost of appending n log entries be characterized, and why?

Choose one answer

2. Operations and Complexity

ConceptPer-op (naive view)Amortized / ActualNotes
Dynamic array appendO(n) on resizeO(1) amortizedGeometric growth (doubling) is the key
n appends totalO(n) totalSum of a geometric series, not O(n^2)
Hash table insertO(n) on rehashO(1) amortizedSame doubling argument as the array
Iterative algorithm spaceO(1)O(n) auxCount only extra allocations
Recursion spaceO(depth)Stack frames; balanced tree → O(log n), skewed → O(n)

The "why it works" intuition for amortized O(1) append: with geometric (doubling) growth, the total copy work over n inserts is 1 + 2 + 4 + ... + n < 2n, so total cost is O(n) and the per-op average is O(1). (Linear growth — adding a constant chunk — would make total work O(n^2), which is why doubling matters.)

Interviewers probe two things: (1) "is that O(1) or O(1) amortized?" — say "amortized" and explain the resize; (2) "what's the space complexity?" — answer in auxiliary terms and explicitly include depth.

3. Implementation

There is no structure to build; the skill is computing the amortized and space bounds correctly.

# Amortized O(1) append, demonstrated by the doubling that hides the O(n) copy.
class DynamicArray:
    def __init__(self):
        self._cap = 1
        self._n = 0
        self._buf = [None] * self._cap

    def append(self, x):
        if self._n == self._cap:        # rare: triggers an O(n) copy
            self._cap *= 2              # DOUBLE -> resize cost amortizes to O(1)
            new_buf = [None] * self._cap
            for i in range(self._n):    # the O(n) copy ...
                new_buf[i] = self._buf[i]
            self._buf = new_buf
        self._buf[self._n] = x          # ... paid for by ~n cheap O(1) appends
        self._n += 1
    # n appends => total copy work 1+2+4+...+n < 2n => O(n) total => O(1) amortized.

# Space complexity: iterative is O(1) auxiliary; recursion costs O(depth).
def sum_iter(nums):
    total = 0
    for x in nums:        # O(1) extra space, regardless of n
        total += x
    return total

def sum_rec(nums, i=0):
    if i == len(nums):    # recursion goes n deep -> O(n) STACK space,
        return 0          # even though no array is allocated.
    return nums[i] + sum_rec(nums, i + 1)

In a real interview you do not implement a dynamic array — Python's built-in list already gives amortized-O(1) append. You cite the amortized argument: "list.append is amortized O(1) because CPython over-allocates geometrically."

QUICK CHECK

A backend service uses a recursive function to traverse a deeply nested JSON configuration tree with up to 10,000 nodes. A colleague suggests replacing it with an iterative version using an explicit stack. From a space-complexity perspective, what is the key difference between the two approaches?

Choose one answer

4. When to Reach for It

  • When you use list.append, dict/set insertion, or collections.deque in a loop → claim amortized O(1), not O(1), and be ready to justify it.
  • When the interviewer asks "is appending n items O(n) or O(n^2)?" → O(n) total, because of geometric growth.
  • Whenever you give a time complexity → immediately give the auxiliary space complexity in the same breath.
  • When you write → state space as O(max depth) and identify whether that depth is log n (balanced) or n (skewed/linear).
QUICK CHECK

A backend service builds a result list by calling list.append() inside a loop that runs n times. What is the correct time complexity to report for the entire append sequence, and why?

Choose one answer

5. Common Pitfalls

  • Saying n appends is O(n^2). Forgetting the geometric-growth argument and counting each resize at full price.
  • Confusing amortized with average-case. Amortized is a guarantee over any sequence (no probability); average-case depends on input distribution. Quicksort is average-O(n log n); dynamic-array append is amortized-O(1).
  • Reporting total space instead of auxiliary. Counting the input array as your space cost — interviewers want the extra memory.
  • Forgetting space. Claiming an in-place recursive algorithm is O(1) space when its is O(n) deep.
  • Claiming amortized O(1) for linear growth. Growing by a fixed chunk instead of doubling makes append amortized O(n) — the doubling is load-bearing.
QUICK CHECK

A backend engineer implements a dynamic buffer that grows by a fixed chunk of 100 elements every time it runs out of space, rather than doubling its capacity. They claim appending n elements is amortized O(1) per append. Why is this claim incorrect?

Choose one answer

6. Interview Cheat Sheet

  • "list.append is amortized O(1): the array doubles on resize, so the O(n) copy is paid back by the next n cheap appends — n appends is O(n) total."
  • "Amortized is a worst-case guarantee over a sequence with no probability; average-case depends on the input distribution — they are not the same."
  • "I report auxiliary space, and depth counts: a balanced is O(log n) space, a skewed one is O(n)."
  • "In-place doesn't always mean O(1) space — if it recurses n deep it's O(n) on the ."

Follow-ups:

  • "Why doesn't appending n items cost O(n^2)?" — Geometric growth: total copy work is 1 + 2 + ... + n < 2n, so O(n) total, O(1) amortized.
  • "Difference between amortized and average-case?" — Amortized: deterministic guarantee over a sequence. Average: expectation over an input distribution. Resize → amortized; quicksort → average.
  • "Your is recursive — what's the space?"O(h) for the call where h is the recursion depth (O(n) worst case for a skewed tree / long path), plus any visited set.