Dynamic Arrays

Week 5, Tuesday (Video)

February 3, 2026

The Problem

How Does Python’s List Work?

items = []
for i in range(1000000):
    items.append(i)  # How can this keep growing?

The mystery: Arrays have fixed size in memory. How does append work when the array is “full”?

The Naive Approach

What if we allocated exactly the space we need?

def append(self, item):
    new_array = allocate(len(self) + 1)  # Make room for one more
    copy all items to new_array           # O(n) work!
    new_array[len(self)] = item
    self.data = new_array

We Need a Better Strategy

Key insight: Don’t resize every time. Resize strategically.

Two strategies to consider:

  1. Add a constant: When full, add \(c\) extra slots
  2. Double: When full, double the capacity

Which is better?

Strategy 1: Add a Constant

Add c=2 Extra Slots Each Time

Starting with capacity 2:

capacity = 2 capacity = 4, copy 2 capacity = 6, copy 4 capacity = 8, copy 6 copy 8 Copies: 2 + 4 + 6 + 8 + ... = O(n²)

Counting the Work

Let \(n = 2^k\) for some \(k\) (makes counting easier).

Resizes happen at sizes 2, 4, 6, 8, …, \(n\).

That’s \(n/2\) resize events, copying \(2 + 4 + 6 + \ldots + n\) items.

\[2 + 4 + 6 + \ldots + n = 2(1 + 2 + 3 + \ldots + n/2) = 2 \cdot \frac{(n/2)(n/2 + 1)}{2}\]

\[= \frac{n}{2} \cdot \frac{n+2}{2} = \frac{n^2 + 2n}{4} = O(n^2)\]

Verdict: \(O(n^2)\) total copies, or \(O(n)\) per append on average.

Strategy 2: Double the Capacity

Double When Full

Starting with capacity 1:

capacity = 1 capacity = 2, copy 1 capacity = 4, copy 2 capacity = 8, copy 4 capacity = 16, copy 8 Copies: 1 + 2 + 4 + 8 + ... = O(n)

Counting the Work

Again, let \(n = 2^k\).

Resizes happen when we fill capacities 1, 2, 4, 8, …, \(n/2\).

That’s \(k\) resize events (at capacities \(2^0, 2^1, \ldots, 2^{k-1}\)).

Total copies: \(1 + 2 + 4 + 8 + \ldots + n/2\)

This is a geometric series!

\[1 + 2 + 4 + \ldots + 2^{k-1} = 2^k - 1 = n - 1 = O(n)\]

Verdict: \(O(n)\) total copies, or \(O(1)\) per append on average!

Observe

The Punchline

Strategy Total work for n appends Average per append
Add constant k \(O(n^2)\) \(O(n)\)
Double \(O(n)\) \(O(1)\)

Doubling wins! This is why Python lists (and Java ArrayLists, C++ vectors) all use doubling.

I think I would have 2 questions…

Amortized Analysis

What is “Amortized” Cost?

Amortized = “averaged over a sequence of operations”

Individual appends have different costs:

  • Most appends: \(O(1)\) (just put item in empty slot)
  • Occasional appends: \(O(n)\) (need to resize and copy)

But averaged over \(n\) operations: \(O(1)\) each!

The Banking Analogy

Think of it like a savings account:

  • Each append “deposits” 3 coins
  • Inserting the item costs 1 coin
  • Save 2 coins for future copying

When we double from size \(n\) to \(2n\):

  • We have \(n\) saved coins (from n/2 cheap appends)
  • Copying costs \(n\) coins
  • We break even!

Amortized \(\neq\) Average Case

Average case: Random inputs, expected behavior

Amortized: Worst case, but spread over many operations

Amortized O(1) is a guarantee:

“Any sequence of n operations takes O(n) time total.”

Why This Matters

Python Lists Use Doubling

import sys

items = []
for i in range(20):
    items.append(i)
    print(f"len={len(items):2d}, size={sys.getsizeof(items)} bytes")

You’ll see the size jump at certain points (the doublings!).

The Tradeoff

Doubling pros:

  • O(1) amortized append
  • Fast!

Doubling cons:

  • Up to 2× wasted space
  • Individual operations can be slow (unpredictable)

For most applications, the speed is worth the space.

Where Else Does This Apply?

Same analysis works for:

  • Hash table resizing (Week 5)
  • Heap arrays (Week 9)
  • Any growable array structure

The doubling trick is everywhere!

Summary

Key Takeaways

  1. Arrays are fixed size — growing requires copying

  2. Add-constant strategy: \(O(n^2)\) for n appends (bad!)

  3. Doubling strategy: \(O(n)\) for n appends (good!)

  4. Amortized O(1): Expensive operations are rare enough to average out

  5. This is how Python lists work!

Coming Up

Wednesday: Back to stacks and queues — exploring mazes with DFS and BFS!