Chapter 8: Arrays and Dynamic Arrays
8.03Beginner / Intermediate7 topics16 min5 interactive animations

Dynamic arrays

A dynamic array hides a fixed array behind a size and a capacity, growing it geometrically when it fills up. This lesson explains capacity expansion, why geometric growth gives amortised O(1) appends, the internal buffer, shrinking and the memory overhead you pay.

Learning goals

  • Explain size versus capacity
  • Compare growth policies and their costs
  • Describe how ArrayList manages its buffer
  • Estimate the memory overhead of a dynamic array

Prerequisites

Core operationsAmortised analysis
8.03.1

Capacity expansion

When size reaches capacity, a dynamic array allocates a bigger buffer, copies the existing elements and continues. The copy costs O(size), but it happens rarely. Try the three growth policies in the animation and watch the copy counters.

Doubling is the textbook rule; java.util.ArrayList grows by 50%.

Java
1void add(int x) {
2 if (size == data.length) data = Arrays.copyOf(data, Math.max(1, data.length * 2));
3 data[size++] = x;
4}

Dynamic array: capacity, growth and shrinking

Interactive · edit the input

Your input

Try

size elements in the buffer · length 0

Used slot (size)Spare capacity

data[] · capacity 1

0
Setup

A new dynamic array starts with capacity 1 and no elements.

Step 1 / 21
Java
1void add(int x) {
2 if (size == data.length) { // buffer is full
3 data = Arrays.copyOf(data, newCapacity()); // allocate bigger, copy size elements
4 }
5 data[size++] = x;
6}
7int removeLast() {
8 int x = data[--size];
9 if (size <= data.length / 4) data = Arrays.copyOf(data, Math.max(1, data.length / 2)); // optional shrink
10 return x;
11}

Live state

0 copies0 resizes

size

0

capacity

1

No condition evaluated on this step
Execution trace (dry run table)Show 21 steps
StepLineStateWhat happens
1L1size=0 capacity=1A new dynamic array starts with capacity 1 and no elements.
2L2size=0 capacity=1 x=4size 0 < capacity 1: there is a spare slot, so adding is O(1).
3L5size=1 capacity=1 x=4Write 4 into data[0] and increase size to 1.
4L2size=1 capacity=1 x=8size 1 equals capacity 1: no room for 8, so the buffer must grow.
5L3size=1 capacity=2Allocate capacity 2 and copy the 1 element (1 copy so far).
6L5size=2 capacity=2 x=8Write 8 into data[1] and increase size to 2.
7L2size=2 capacity=2 x=15size 2 equals capacity 2: no room for 15, so the buffer must grow.
8L3size=2 capacity=4Allocate capacity 4 and copy the 2 elements (3 copys so far).
9L5size=3 capacity=4 x=15Write 15 into data[2] and increase size to 3.
10L2size=3 capacity=4 x=16size 3 < capacity 4: there is a spare slot, so adding is O(1).
11L5size=4 capacity=4 x=16Write 16 into data[3] and increase size to 4.
12L2size=4 capacity=4 x=23size 4 equals capacity 4: no room for 23, so the buffer must grow.
13L3size=4 capacity=8Allocate capacity 8 and copy the 4 elements (7 copys so far).
14L5size=5 capacity=8 x=23Write 23 into data[4] and increase size to 5.
15L2size=5 capacity=8 x=42size 5 < capacity 8: there is a spare slot, so adding is O(1).
16L5size=6 capacity=8 x=42Write 42 into data[5] and increase size to 6.
17L2size=6 capacity=8 x=7size 6 < capacity 8: there is a spare slot, so adding is O(1).
18L5size=7 capacity=8 x=7Write 7 into data[6] and increase size to 7.
19L2size=7 capacity=8 x=9size 7 < capacity 8: there is a spare slot, so adding is O(1).
20L5size=8 capacity=8 x=9Write 9 into data[7] and increase size to 8.
21L11size=8 capacity=8 copies=7Final: size 8, capacity 8 (0 spare slots), 3 resizes that copied 7 elements in total. Doubling keeps total copies below 2n.
8.03.2

Geometric growth

Geometric growth multiplies the capacity by a constant factor (2, 1.5, ...). Because the capacities form a geometric series, the total copy work for n appends is a constant multiple of n: with doubling, 1 + 2 + 4 + ... + n/2 < n. Growing by a fixed step (+10) instead makes the copies add up to about n²/20.

Pick the growth policy and compare the total copies.

PolicyCapacity after growthTotal copy work for n appends
Doubling2 × old< n
×1.5 (ArrayList)old + old / 2up to about 2n
Fixed step cold + cabout n² / (2c)

Dynamic array: amortised cost of append

Interactive · edit the input

Your input

Try

backing array (capacity 1)

·

cost of each append

empty
Setup

Start with capacity 1. Each append costs 1 write, plus size copies whenever the array is full. Policy: Double (×2).

Step 1 / 24
Java
1void add(int x) {
2 if (size == data.length) { // full: allocate a bigger array
3 data = Arrays.copyOf(data, newCapacity(data.length)); // copies `size` elements
4 }
5 data[size++] = x; // the cheap part: 1 write
6}
7// doubling: newCapacity = 2 * capacity; grow-by-1: capacity + 1; Java ArrayList: capacity + capacity / 2

Live state

0 copies0 writes

capacity

1

size

0

No condition evaluated on this step
Execution trace (dry run table)Show 24 steps
StepLineStateWhat happens
1L1capacity=1 size=0Start with capacity 1. Each append costs 1 write, plus size copies whenever the array is full. Policy: Double (×2).
2L5size=1 capacity=1 cost of this append=1 total cost=1 amortised=1Append #1 costs 1. Total so far 1, so the average is 1.00 per append.
3L3capacity=1 new capacity=2 copies=1Full: allocate 2 slots and copy 1 element. This single append costs 2.
4L5size=2 capacity=2 cost of this append=2 total cost=3 amortised=1.5Append #2 costs 2. Total so far 3, so the average is 1.50 per append.
5L3capacity=2 new capacity=4 copies=2Full: allocate 4 slots and copy 2 elements. This single append costs 3.
6L5size=3 capacity=4 cost of this append=3 total cost=6 amortised=2Append #3 costs 3. Total so far 6, so the average is 2.00 per append.
7L5size=4 capacity=4 cost of this append=1 total cost=7 amortised=1.75Append #4 costs 1. Total so far 7, so the average is 1.75 per append.
8L3capacity=4 new capacity=8 copies=4Full: allocate 8 slots and copy 4 elements. This single append costs 5.
9L5size=5 capacity=8 cost of this append=5 total cost=12 amortised=2.4Append #5 costs 5. Total so far 12, so the average is 2.40 per append.
10L5size=6 capacity=8 cost of this append=1 total cost=13 amortised=2.17Append #6 costs 1. Total so far 13, so the average is 2.17 per append.
11L5size=7 capacity=8 cost of this append=1 total cost=14 amortised=2Append #7 costs 1. Total so far 14, so the average is 2.00 per append.
12L5size=8 capacity=8 cost of this append=1 total cost=15 amortised=1.88Append #8 costs 1. Total so far 15, so the average is 1.88 per append.
13L3capacity=8 new capacity=16 copies=8Full: allocate 16 slots and copy 8 elements. This single append costs 9.
14L5size=9 capacity=16 cost of this append=9 total cost=24 amortised=2.67Append #9 costs 9. Total so far 24, so the average is 2.67 per append.
15L5size=10 capacity=16 cost of this append=1 total cost=25 amortised=2.5Append #10 costs 1. Total so far 25, so the average is 2.50 per append.
16L5size=11 capacity=16 cost of this append=1 total cost=26 amortised=2.36Append #11 costs 1. Total so far 26, so the average is 2.36 per append.
17L5size=12 capacity=16 cost of this append=1 total cost=27 amortised=2.25Append #12 costs 1. Total so far 27, so the average is 2.25 per append.
18L5size=13 capacity=16 cost of this append=1 total cost=28 amortised=2.15Append #13 costs 1. Total so far 28, so the average is 2.15 per append.
19L5size=14 capacity=16 cost of this append=1 total cost=29 amortised=2.07Append #14 costs 1. Total so far 29, so the average is 2.07 per append.
20L5size=15 capacity=16 cost of this append=1 total cost=30 amortised=2Append #15 costs 1. Total so far 30, so the average is 2.00 per append.
21L5size=16 capacity=16 cost of this append=1 total cost=31 amortised=1.94Append #16 costs 1. Total so far 31, so the average is 1.94 per append.
22L3capacity=16 new capacity=32 copies=16Full: allocate 32 slots and copy 16 elements. This single append costs 17.
23L5size=17 capacity=32 cost of this append=17 total cost=48 amortised=2.82Append #17 costs 17. Total so far 48, so the average is 2.82 per append.
24L7appends=17 total cost=48 return=2.824Doubling: total cost 48 < 3n = 51, so the amortised cost per append is O(1) (here 2.82). The expensive copies are rare and pay for themselves: aggregate analysis. Accounting method: charge 3 coins per append: 1 for the write, 1 to copy itself later, 1 to copy an older element.
8.03.3

Amortized append

An individual append can cost O(n) when it triggers a resize, but any sequence of n appends costs O(n) in total, so the amortised cost per append is O(1). The guarantee is about the whole sequence, not about the average input.

The chart plots the cost of each append; the occasional tall bar is paid for by the many cheap ones.

Dynamic array: amortised cost of append

Interactive · edit the input

Your input

Try

backing array (capacity 1)

·

cost of each append

empty
Setup

Start with capacity 1. Each append costs 1 write, plus size copies whenever the array is full. Policy: Double (×2).

Step 1 / 24
Java
1void add(int x) {
2 if (size == data.length) { // full: allocate a bigger array
3 data = Arrays.copyOf(data, newCapacity(data.length)); // copies `size` elements
4 }
5 data[size++] = x; // the cheap part: 1 write
6}
7// doubling: newCapacity = 2 * capacity; grow-by-1: capacity + 1; Java ArrayList: capacity + capacity / 2

Live state

0 copies0 writes

capacity

1

size

0

No condition evaluated on this step
Execution trace (dry run table)Show 24 steps
StepLineStateWhat happens
1L1capacity=1 size=0Start with capacity 1. Each append costs 1 write, plus size copies whenever the array is full. Policy: Double (×2).
2L5size=1 capacity=1 cost of this append=1 total cost=1 amortised=1Append #1 costs 1. Total so far 1, so the average is 1.00 per append.
3L3capacity=1 new capacity=2 copies=1Full: allocate 2 slots and copy 1 element. This single append costs 2.
4L5size=2 capacity=2 cost of this append=2 total cost=3 amortised=1.5Append #2 costs 2. Total so far 3, so the average is 1.50 per append.
5L3capacity=2 new capacity=4 copies=2Full: allocate 4 slots and copy 2 elements. This single append costs 3.
6L5size=3 capacity=4 cost of this append=3 total cost=6 amortised=2Append #3 costs 3. Total so far 6, so the average is 2.00 per append.
7L5size=4 capacity=4 cost of this append=1 total cost=7 amortised=1.75Append #4 costs 1. Total so far 7, so the average is 1.75 per append.
8L3capacity=4 new capacity=8 copies=4Full: allocate 8 slots and copy 4 elements. This single append costs 5.
9L5size=5 capacity=8 cost of this append=5 total cost=12 amortised=2.4Append #5 costs 5. Total so far 12, so the average is 2.40 per append.
10L5size=6 capacity=8 cost of this append=1 total cost=13 amortised=2.17Append #6 costs 1. Total so far 13, so the average is 2.17 per append.
11L5size=7 capacity=8 cost of this append=1 total cost=14 amortised=2Append #7 costs 1. Total so far 14, so the average is 2.00 per append.
12L5size=8 capacity=8 cost of this append=1 total cost=15 amortised=1.88Append #8 costs 1. Total so far 15, so the average is 1.88 per append.
13L3capacity=8 new capacity=16 copies=8Full: allocate 16 slots and copy 8 elements. This single append costs 9.
14L5size=9 capacity=16 cost of this append=9 total cost=24 amortised=2.67Append #9 costs 9. Total so far 24, so the average is 2.67 per append.
15L5size=10 capacity=16 cost of this append=1 total cost=25 amortised=2.5Append #10 costs 1. Total so far 25, so the average is 2.50 per append.
16L5size=11 capacity=16 cost of this append=1 total cost=26 amortised=2.36Append #11 costs 1. Total so far 26, so the average is 2.36 per append.
17L5size=12 capacity=16 cost of this append=1 total cost=27 amortised=2.25Append #12 costs 1. Total so far 27, so the average is 2.25 per append.
18L5size=13 capacity=16 cost of this append=1 total cost=28 amortised=2.15Append #13 costs 1. Total so far 28, so the average is 2.15 per append.
19L5size=14 capacity=16 cost of this append=1 total cost=29 amortised=2.07Append #14 costs 1. Total so far 29, so the average is 2.07 per append.
20L5size=15 capacity=16 cost of this append=1 total cost=30 amortised=2Append #15 costs 1. Total so far 30, so the average is 2.00 per append.
21L5size=16 capacity=16 cost of this append=1 total cost=31 amortised=1.94Append #16 costs 1. Total so far 31, so the average is 1.94 per append.
22L3capacity=16 new capacity=32 copies=16Full: allocate 32 slots and copy 16 elements. This single append costs 17.
23L5size=17 capacity=32 cost of this append=17 total cost=48 amortised=2.82Append #17 costs 17. Total so far 48, so the average is 2.82 per append.
24L7appends=17 total cost=48 return=2.824Doubling: total cost 48 < 3n = 51, so the amortised cost per append is O(1) (here 2.82). The expensive copies are rare and pay for themselves: aggregate analysis. Accounting method: charge 3 coins per append: 1 for the write, 1 to copy itself later, 1 to copy an older element.
8.03.4

Capacity vs size

Size is how many elements the user has stored; capacity is how many the buffer can hold. Always size ≤ capacity. ArrayList.size() reports the size and there is no public capacity getter, but you can reserve with ensureCapacity and release spare room with trimToSize.

Presizing with the expected count avoids every intermediate resize.

Java
1List<Integer> xs = new ArrayList<>(1000); // capacity 1000, size 0
2xs.add(1); // size 1
3ArrayList<Integer> big = new ArrayList<>();
4big.ensureCapacity(5000); // one allocation up front
5big.trimToSize(); // capacity shrinks to size

Dynamic array: capacity, growth and shrinking

Interactive · edit the input

Your input

Try

size elements in the buffer · length 0

Used slot (size)Spare capacity

data[] · capacity 1

0
Setup

A new dynamic array starts with capacity 1 and no elements.

Step 1 / 21
Java
1void add(int x) {
2 if (size == data.length) { // buffer is full
3 data = Arrays.copyOf(data, newCapacity()); // allocate bigger, copy size elements
4 }
5 data[size++] = x;
6}
7int removeLast() {
8 int x = data[--size];
9 if (size <= data.length / 4) data = Arrays.copyOf(data, Math.max(1, data.length / 2)); // optional shrink
10 return x;
11}

Live state

0 copies0 resizes

size

0

capacity

1

No condition evaluated on this step
Execution trace (dry run table)Show 21 steps
StepLineStateWhat happens
1L1size=0 capacity=1A new dynamic array starts with capacity 1 and no elements.
2L2size=0 capacity=1 x=4size 0 < capacity 1: there is a spare slot, so adding is O(1).
3L5size=1 capacity=1 x=4Write 4 into data[0] and increase size to 1.
4L2size=1 capacity=1 x=8size 1 equals capacity 1: no room for 8, so the buffer must grow.
5L3size=1 capacity=2Allocate capacity 2 and copy the 1 element (1 copy so far).
6L5size=2 capacity=2 x=8Write 8 into data[1] and increase size to 2.
7L2size=2 capacity=2 x=15size 2 equals capacity 2: no room for 15, so the buffer must grow.
8L3size=2 capacity=4Allocate capacity 4 and copy the 2 elements (3 copys so far).
9L5size=3 capacity=4 x=15Write 15 into data[2] and increase size to 3.
10L2size=3 capacity=4 x=16size 3 < capacity 4: there is a spare slot, so adding is O(1).
11L5size=4 capacity=4 x=16Write 16 into data[3] and increase size to 4.
12L2size=4 capacity=4 x=23size 4 equals capacity 4: no room for 23, so the buffer must grow.
13L3size=4 capacity=8Allocate capacity 8 and copy the 4 elements (7 copys so far).
14L5size=5 capacity=8 x=23Write 23 into data[4] and increase size to 5.
15L2size=5 capacity=8 x=42size 5 < capacity 8: there is a spare slot, so adding is O(1).
16L5size=6 capacity=8 x=42Write 42 into data[5] and increase size to 6.
17L2size=6 capacity=8 x=7size 6 < capacity 8: there is a spare slot, so adding is O(1).
18L5size=7 capacity=8 x=7Write 7 into data[6] and increase size to 7.
19L2size=7 capacity=8 x=9size 7 < capacity 8: there is a spare slot, so adding is O(1).
20L5size=8 capacity=8 x=9Write 9 into data[7] and increase size to 8.
21L11size=8 capacity=8 copies=7Final: size 8, capacity 8 (0 spare slots), 3 resizes that copied 7 elements in total. Doubling keeps total copies below 2n.
8.03.5

Internal buffer

Inside, a dynamic array is just an array plus a counter. ArrayList keeps an Object[] elementData and an int size; its first add allocates capacity 10, and growth is old + (old >> 1). Elements at index size and beyond are null so the garbage collector can reclaim removed objects.

A minimal version shows every part.

Java
1class IntList {
2 private int[] data = new int[4];
3 private int size;
4 void add(int x) {
5 if (size == data.length) data = Arrays.copyOf(data, size * 2);
6 data[size++] = x;
7 }
8 int get(int i) {
9 if (i < 0 || i >= size) throw new IndexOutOfBoundsException("index " + i + ", size " + size);
10 return data[i];
11 }
12 int size() { return size; }
13}
8.03.6

Shrinking

Shrinking releases memory after many removals. A naive rule, halve when size drops to half of capacity, can thrash: alternating add and remove at the boundary would resize every time. The standard fix is to shrink only when size falls to a quarter of capacity, halving the buffer so it ends up half full.

Use the removal field in the doubling policy to see the shrink step. ArrayList itself never shrinks automatically; call trimToSize() if memory matters.

Dynamic array: capacity, growth and shrinking

Interactive · edit the input

Your input

Try

size elements in the buffer · length 0

Used slot (size)Spare capacity

data[] · capacity 1

0
Setup

A new dynamic array starts with capacity 1 and no elements.

Step 1 / 21
Java
1void add(int x) {
2 if (size == data.length) { // buffer is full
3 data = Arrays.copyOf(data, newCapacity()); // allocate bigger, copy size elements
4 }
5 data[size++] = x;
6}
7int removeLast() {
8 int x = data[--size];
9 if (size <= data.length / 4) data = Arrays.copyOf(data, Math.max(1, data.length / 2)); // optional shrink
10 return x;
11}

Live state

0 copies0 resizes

size

0

capacity

1

No condition evaluated on this step
Execution trace (dry run table)Show 21 steps
StepLineStateWhat happens
1L1size=0 capacity=1A new dynamic array starts with capacity 1 and no elements.
2L2size=0 capacity=1 x=4size 0 < capacity 1: there is a spare slot, so adding is O(1).
3L5size=1 capacity=1 x=4Write 4 into data[0] and increase size to 1.
4L2size=1 capacity=1 x=8size 1 equals capacity 1: no room for 8, so the buffer must grow.
5L3size=1 capacity=2Allocate capacity 2 and copy the 1 element (1 copy so far).
6L5size=2 capacity=2 x=8Write 8 into data[1] and increase size to 2.
7L2size=2 capacity=2 x=15size 2 equals capacity 2: no room for 15, so the buffer must grow.
8L3size=2 capacity=4Allocate capacity 4 and copy the 2 elements (3 copys so far).
9L5size=3 capacity=4 x=15Write 15 into data[2] and increase size to 3.
10L2size=3 capacity=4 x=16size 3 < capacity 4: there is a spare slot, so adding is O(1).
11L5size=4 capacity=4 x=16Write 16 into data[3] and increase size to 4.
12L2size=4 capacity=4 x=23size 4 equals capacity 4: no room for 23, so the buffer must grow.
13L3size=4 capacity=8Allocate capacity 8 and copy the 4 elements (7 copys so far).
14L5size=5 capacity=8 x=23Write 23 into data[4] and increase size to 5.
15L2size=5 capacity=8 x=42size 5 < capacity 8: there is a spare slot, so adding is O(1).
16L5size=6 capacity=8 x=42Write 42 into data[5] and increase size to 6.
17L2size=6 capacity=8 x=7size 6 < capacity 8: there is a spare slot, so adding is O(1).
18L5size=7 capacity=8 x=7Write 7 into data[6] and increase size to 7.
19L2size=7 capacity=8 x=9size 7 < capacity 8: there is a spare slot, so adding is O(1).
20L5size=8 capacity=8 x=9Write 9 into data[7] and increase size to 8.
21L11size=8 capacity=8 copies=7Final: size 8, capacity 8 (0 spare slots), 3 resizes that copied 7 elements in total. Doubling keeps total copies below 2n.
8.03.7

Memory overhead

A dynamic array wastes the spare capacity: right after a doubling the buffer is only half full, so up to 50% of the slots are unused (33% for ×1.5). During a resize both the old and the new buffer exist, so peak memory is about 1.5× to 3× the final size. On top of that, ArrayList<Integer> stores references plus a boxed object per element.

For large primitive data prefer int[] with your own size counter.

Storage of 1,000,000 intsApproximate memory
int[] (exact)4 MB
Dynamic int buffer, worst case just after doublingup to 8 MB (+4 MB briefly while copying)
ArrayList<Integer>about 4 MB references + 16 MB Integer objects

Practice ladder

Now try it yourself

Easy

  • How many resizes happen for 100 appends with doubling from capacity 1?
  • What is the difference between size and capacity?

Medium

  • Show that a shrink at size = capacity / 2 can cost O(n) per operation
  • Why does ArrayList clear the slot of a removed element?

Advanced

  • Prove that growth by a constant factor g > 1 gives amortised O(1) appends and find the total copies for g = 1.5

Key takeaways

  • A dynamic array is an array plus size; resize by a constant factor.
  • Geometric growth gives amortised O(1) appends; fixed-step growth does not.
  • Spare capacity and boxed elements are the memory cost; presize when you can.