Amortized and Space Complexity
5 min read
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.
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?
2. Operations and Complexity
| Concept | Per-op (naive view) | Amortized / Actual | Notes |
|---|---|---|---|
Dynamic array append | O(n) on resize | O(1) amortized | Geometric growth (doubling) is the key |
n appends total | — | O(n) total | Sum of a geometric series, not O(n^2) |
Hash table insert | O(n) on rehash | O(1) amortized | Same doubling argument as the array |
| Iterative algorithm space | — | O(1)–O(n) aux | Count only extra allocations |
| Recursion space | — | O(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."
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?
4. When to Reach for It
- When you use
list.append,dict/setinsertion, orcollections.dequein a loop → claim amortizedO(1), notO(1), and be ready to justify it. - When the interviewer asks "is appending
nitemsO(n)orO(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 islog n(balanced) orn(skewed/linear).
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?
5. Common Pitfalls
- Saying
nappends isO(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 isO(n)deep. - Claiming amortized
O(1)for linear growth. Growing by a fixed chunk instead of doubling makes append amortizedO(n)— the doubling is load-bearing.
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?
6. Interview Cheat Sheet
- "
list.appendis amortizedO(1): the array doubles on resize, so theO(n)copy is paid back by the nextncheap appends —nappends isO(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 isO(n)." - "In-place doesn't always mean
O(1)space — if it recursesndeep it'sO(n)on the ."
Follow-ups:
- "Why doesn't appending
nitems costO(n^2)?" — Geometric growth: total copy work is1 + 2 + ... + n < 2n, soO(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 wherehis the recursion depth (O(n)worst case for a skewed tree / long path), plus any visited set.
Glossary History
Click dotted jargon to save explanations here.
Glossary History
Click dotted jargon to save explanations here.