Dynamic Arrays
Core idea
A dynamic array is an array-like data structure that can automatically grow when its current storage becomes full.
Python’s list behaves like a dynamic array.
Size vs Capacity
This distinction is fundamental.
- Size = number of elements currently stored.
- Capacity = total number of elements the allocated storage can currently hold.
Example:
[10, 20, 30, __, __]
size = 3
capacity = 5
Therefore:
size <= capacity
Append when capacity is available
Suppose:
size = 3
capacity = 5
[10, 20, 30, __, __]
Appending 40 simply uses the next free slot:
[10, 20, 30, 40, __]
No elements need to move.
append → O(1)
What happens when capacity is full?
Suppose:
size = 5
capacity = 5
[10, 20, 30, 40, 50]
Appending another element requires more storage.
Conceptually:
Old capacity = 5
↓
Allocate larger array
↓
Copy existing elements
↓
Add new element
If the new capacity is larger by a multiplicative growth factor, such as roughly 2×:
Old:
[10, 20, 30, 40, 50]
New:
[10, 20, 30, 40, 50, __, __, __, __, __]
Copying the existing n elements costs:
O(n)
Therefore:
Append causing resize → O(n) worst case
Amortized O(1)
This is the most important concept in dynamic arrays.
A single append can be expensive:
Normal append → O(1)
Append causing resize → O(n)
But resizing does not happen on every append.
If capacity grows geometrically:
1 → 2 → 4 → 8 → 16 → 32 → ...
the total amount of copying over many appends is linear.
For n appends, the total copying is:
1 + 2 + 4 + 8 + ... < 2n
Therefore:
Total cost of n appends = O(n)
Amortized cost per append:
O(n) / n = O(1)
Important
Amortized O(1) does NOT mean every append is O(1).
It means that across a long sequence of appends, the average cost per append is O(1).
Example
arr = []
for i in range(n):
arr.append(i)
There are n appends.
Although some individual appends trigger resizing, the total work is O(n).
Therefore:
Overall time → O(n)
Amortized append → O(1)
Insertions
Consider:
arr.insert(0, 100)
For:
[10, 20, 30, 40]
the result must be:
[100, 10, 20, 30, 40]
The existing elements have to shift right.
Therefore:
Insert at beginning → O(n)
Insert in middle → O(n)
Even if the dynamic array has spare capacity, shifting elements still costs O(n).
Deletions
Deleting from the beginning or middle also requires shifting:
[10, 20, 30, 40, 50]
delete 30
[10, 20, 40, 50]
Therefore:
Delete beginning → O(n)
Delete middle → O(n)
Delete end → O(1)
Dynamic Array Complexity
| Operation | Complexity |
|---|---|
Access arr[i] |
O(1) |
Update arr[i] |
O(1) |
| Search | O(n) |
| Append | O(1) amortized |
| Append causing resize | O(n) worst case |
| Insert beginning | O(n) |
| Insert middle | O(n) |
| Delete beginning | O(n) |
| Delete middle | O(n) |
| Delete end | O(1) |
Why grow geometrically?
If capacity increased by only one:
1 → 2 → 3 → 4 → 5 → 6 → ...
the array would need to resize very frequently.
Instead, dynamic arrays increase capacity by a multiplicative growth factor:
1 → 2 → 4 → 8 → 16 → 32 → ...
This makes resizing sufficiently infrequent to provide amortized O(1) append performance.
The exact growth policy depends on the implementation.
Python
Python’s list is a dynamic array:
nums = []
nums.append(10)
nums.append(20)
nums.append(30)
Conceptually, Python manages the underlying storage and resizing for you.
For interview analysis:
list.append(x)
→ O(1) amortized
→ O(n) worst case when resizing occurs
Static vs Dynamic Array
| Feature | Static Array | Dynamic Array |
|---|---|---|
| Size | Fixed | Can grow |
| Contiguous storage | Yes | Yes |
| Index access | O(1) | O(1) |
| Search | O(n) | O(n) |
| Append | O(1) if capacity exists | O(1) amortized |
| Resize | Manual/new allocation | Automatic |
Mental model
Dynamic Array
↓
size + capacity
↓
Capacity available?
↙ ↘
yes no
↓ ↓
append resize
O(1) copy elements
O(n)
Across many appends:
n appends
↓
O(n) total work
↓
O(1) amortized per append
Interview answers to memorize
Why is array access O(1)?
Arrays use contiguous memory, so the address of an indexed element can be calculated directly.
Why can dynamic-array append be O(n)?
When capacity is full, a larger array must be allocated and the existing elements copied.
Why is append amortized O(1)?
Resizing happens only occasionally, and geometric capacity growth makes the total cost of resizing across many appends O(n), giving O(1) amortized cost per append.
Why is insertion at the beginning O(n)?
Existing elements must be shifted to make room for the new element.
Quick Revision
Dynamic Array:
- Has size and capacity
- Size = elements currently stored
- Capacity = allocated slots
- Access → O(1)
- Update → O(1)
- Search → O(n)
- Append → O(1) amortized
- Resize-triggering append → O(n) worst case
- Insert beginning/middle → O(n)
- Delete beginning/middle → O(n)
- Delete end → O(1)
Key concept:
Amortized O(1) ≠ every operation is O(1).
It means:
Total cost over many operations / number of operations = O(1)