DSA Notes
Understand dynamic arrays from the ground up — how automatic resizing works, why append is O(1) amortized, how Python lists and Java ArrayLists are implemented, amortized analysis with the potential method, and how to implement your own dynamic array from scratch.
Introduction
Every time you use a Python list or a Java ArrayList, you are using a dynamic array — a data structure that looks like a simple array but automatically resizes itself as you add elements.
The fact that append() in Python is O(1) amortized despite occasionally requiring O(n) copying might seem like magic. It is not magic — it is a beautifully designed algorithm based on the doubling strategy, and understanding it gives you a deep intuition for amortized analysis that applies far beyond just arrays.
This lesson dissects dynamic arrays completely: the problem they solve, the doubling solution, the formal amortized analysis, their implementation in major languages, and how to build one yourself.
The Doubling Strategy
Rule: When the internal array is full, allocate a new array with twice the current capacity.
| Start | capacity=1, size=0, array=[_] |
| append(1) | size=1, capacity=1, array=[1] |
| append(2): FULL | allocate capacity=2, copy [1], add 2 → [1,2] |
| append(3): FULL | allocate capacity=4, copy [1,2], add 3 → [1,2,3,_] |
| append(4) | size=3, capacity=4, array=[1,2,3,4] |
| append(5): FULL | allocate capacity=8, copy [1,2,3,4], add 5 → [1,2,3,4,5,_,_,_] |
| append(6) | size=5, [1,2,3,4,5,6,_,_] |
| append(7) | size=6, [1,2,3,4,5,6,7,_] |
| append(8) | size=7, [1,2,3,4,5,6,7,8] |
| append(9): FULL | allocate capacity=16, copy 8 elements, add 9 → [1..9,_,_,_,_,_,_,_] |
Resizing (expensive copies) happens at sizes 1, 2, 4, 8, 16, 32, ... (powers of 2).
Amortized Analysis — Why Append is O(1)
Aggregate Method
Total cost of n appends = (cost of actual appends) + (total copy cost during all resizes)
Copy costs occur at: sizes 1, 2, 4, 8, 16, ..., n/2 (last resize before n elements)
Total cost of n appends:
- n regular appends: O(n)
- Total copies across all resizes: O(n)
- Grand total: O(2n) = O(n)
Average cost per append: O(n) / n = O(1)
This is the amortized O(1) — averaged over a sequence of operations, each append costs O(1).
Potential Method (More Rigorous)
Define the potential function Φ = 2 × size - capacity
Every operation has amortized cost O(1), confirming that the total cost over n operations is O(n).
Python List Internals
Python's list is a dynamic array implemented in C (CPython). Key details:
Growth Factor
Python does not exactly double. It uses the formula:
This gives a growth factor slightly less than double (approximately 1.125×) to avoid over-allocation. The sequence of capacities for a Python list starting empty:
Time Complexities
Shrinking Behavior
Python also shrinks the array when elements are removed, but uses a more conservative threshold (shrinks when size drops below capacity/4 or similar) to avoid thrashing.
Java ArrayList and C++ std::vector
Java ArrayList
import java.util.ArrayList;
ArrayList<Integer> list = new ArrayList<>(); // Initial capacity = 10
list.add(5); // O(1) amortized
list.add(0, 5); // O(n) — insert at front
list.remove(0); // O(n) — remove from front
list.get(2); // O(1) — random access
list.size(); // O(1)
// Initial capacity hint (avoid early resizes)
ArrayList<Integer> list = new ArrayList<>(1000);Java's ArrayList uses growth factor 1.5× (adds 50% each resize): new_capacity = old_capacity + (old_capacity >> 1) = old_capacity * 1.5
C++ std::vector
C++ std::vector uses growth factor 2× (doubles each resize) per most implementations.
Building Your Own Dynamic Array
Testing our implementation:
Memory Usage Analysis
Summary
- Static arrays have fixed size — resizing requires manual allocation and copying
- Dynamic arrays resize automatically using the doubling strategy
- Doubling: When full, allocate 2× capacity, copy all elements
- Amortized analysis: Total copy cost over n appends = O(n), so average per append = O(1)
- Python list: Growth factor ≈ 1.125, O(1) append/pop from end, O(n) insert/remove from middle
- Java ArrayList: Growth factor 1.5×
- C++ std::vector: Growth factor 2×
- The same amortized O(1) trick appears in: hash table resizing, stack using array, queue using circular buffer
*Next Lesson: Multidimensional Arrays — 2D and 3D arrays, matrix operations, and the row-major vs. column-major memory layout.*
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for Dynamic Arrays — How Python Lists and ArrayLists Work Internally.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this Data Structures & Algorithms topic.
Search Terms
data-structures-algorithms, data structures & algorithms, data, structures, algorithms, arrays, dynamic, dynamic arrays — how python lists and arraylists work internally
Related Data Structures & Algorithms Topics