This chapter focuses on building a custom dynamic array data structure using fixed-size arrays, mimicking what languages like Python or JavaScript offer natively.
Implement a dynamic array from scratch using only fixed-size arrays. Avoid using built-in append()
methods.
append(x)
: Adds element x
to the end of the array.get(i)
: Returns the element at index i
.set(i, x)
: Updates the element at index i
to value x
.size()
: Returns the number of elements in the array.pop_back()
: Removes the last element in the array.d = DynamicArray()
d.append(1)
d.append(2)
d.get(0) # returns 1
d.get(1) # returns 2
d.size() # returns 2
d = DynamicArray()
d.append(1)
d.set(0, 10)
d.get(0) # returns 10
d = DynamicArray()
d.append(1)
d.append(2)
d.pop_back()
d.size() # returns 1
d.get(0) # returns 1
The core challenge of implementing a dynamic array using only fixed-size arrays is managing memory manually.
When we double capacity on each overflow, the amortized cost of append
is O(1):
Total cost of n appends:
= n (copy during last resize)
+ n/2 (previous resize)
+ n/4 ...
< 2n → O(n)
So, each append ≈ O(1) on average.
pop_back()
To save memory, we shrink the capacity when the array usage drops below 25%:
Strategy | Outcome |
---|---|
Never shrink | ❌ Wastes memory |
Shrink at 50% | ❌ Can lead to immediate resizing |
✅ Shrink at 25% | ✅ Balanced resizing, prevents bouncing |
n
is the number of stored elements.Before Append:
[1, 2, 3, _, _, _, _, _, _, _] (capacity = 10, size = 3)
Append(4)
After Append:
[1, 2, 3, 4, _, _, _, _, _, _] (size = 4)
If capacity is reached:
Before Append (Full):
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] (capacity = 10)
→ Resize → New capacity = 20
After Resize:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, _, _, _, _, _, _, _, _, _, _]
append()
used internally — resizing logic is handled manually.Enhance the previous DynamicArray
by adding the following methods:
pop(i)
– Removes the element at index i
, shifts all elements after it to the left.contains(x)
– Returns True
if element x
exists in the array.insert(i, x)
– Inserts x
at index i
, shifting other elements right.remove(x)
– Removes first occurrence of x
, shifts remaining elements, returns index or -1
if not found.pop(i)
d = DynamicArrayExtras()
d.append(1)
d.append(2)
d.append(3)
d.pop(1) # returns 2
d.get(1) # returns 3
d.size() # returns 2
contains(x)
d = DynamicArrayExtras()
d.append(1)
d.append(2)
d.contains(1) # returns True
d.contains(3) # returns False
insert(i, x)
d = DynamicArrayExtras()
d.append(1)
d.append(2)
d.insert(1, 3) # array becomes [1, 3, 2]
d.get(1) # returns 3
remove(x)
d = DynamicArrayExtras()
d.append(1)
d.append(2)
d.append(2)
d.remove(2) # returns 1
d.get(1) # returns 2
Implementing extra operations on a dynamic array introduces element shifting and sequential scans. Here’s how each is tackled idiomatically in Go.
pop(i)
— Remove Element at IndexThere are two scenarios:
i == size-1
: We use pop_back()
directly.i
.Before pop(2):
[1, 2, 3, 4, _]
After shifting:
[1, 2, 4, _, _] (size--)
Worst-case: O(n), especially when popping from index 0.
⚠️ Cannot amortize this: doing n pops from the front = O(n²)
contains(x)
— Linear ScanWe scan the array until we find x
.
for i := 0; i < size; i++ {
if data[i] == x {
return true
}
}
return false
insert(i, x)
— Insert at IndexInsert requires right-shifting all elements from index i
onward.
Steps:
i
.x
at i
.Before insert(1, 9):
[1, 2, 3, _, _]
Shift → [1, _, 2, 3, _]
Insert → [1, 9, 2, 3, _]
remove(x)
— Remove First OccurrenceThis combines contains()
and pop(i)
.
Steps:
i
where x
exists.pop(i)
to shift elements left.i
or -1
if not found.Before: [1, 2, 3, 2, 4]
remove(2) → [1, 3, 2, 4, _] → returns 1
Operation | Time Complexity | Amortized? | Comments |
---|---|---|---|
append(x) |
O(1) | ✅ | Uses doubling |
pop_back() |
O(1) | ✅ | Shrinks at 25% |
pop(i) |
O(n) | ❌ | Shifting |
insert(i,x) |
O(n) | ❌ | Right-shifting |
remove(x) |
O(n) | ❌ | Linear search + shift |
contains(x) |
O(n) | ❌ | Linear search |
If frequent operations are needed at both ends or middle, consider:
deque
(double-ended queue) for O(1) front/back operations.linked list
for constant-time inserts/removes.hash table
or set
for faster membership testing.