How does the underlying array, length, and capacity of a Go slice work?
Understand how a Go slice's backing array, length, and capacity work, when append reallocates, and how slices share memory — with clear examples.
Expected Interview Answer
A Go slice is a small header with three fields — a pointer to an underlying array, a length (how many elements are currently usable), and a capacity (how many elements fit from the pointer to the end of the array). The slice itself holds no data; it is a view into that shared backing array.
Length is what len() returns and bounds normal indexing; capacity is what cap() returns and bounds re-slicing. Slicing with s[low:high] creates a new header pointing into the same array, so two slices can share and mutate the same data. When you append past the current capacity, Go allocates a new, larger backing array, copies the elements over, and returns a slice pointing at the new array — at which point the old and new slices no longer share storage.
- Slicing and passing slices is cheap — only the 3-word header is copied
- Multiple slices can view the same array without copying
- append grows capacity automatically, amortizing allocations
- len and cap give precise control over reads and re-slicing
- Reserving capacity up front avoids repeated reallocation
AI Mentor Explanation
Picture a full stadium of numbered seats as the backing array. A slice is like a printed block-booking ticket: it names the first seat, says how many seats your group currently occupies (length), and how many remain in that block before the wall (capacity). Adding more fans is fine while empty seats remain, but once the block is full the stadium reassigns you to a brand-new, larger block and everyone shuffles across.
Step-by-Step Explanation
Step 1
Understand the header
A slice value is three words: a pointer to a backing array element, a length, and a capacity. Copying a slice copies only these three fields.
Step 2
Length bounds indexing
len(s) is how many elements you can read or write with s[i]; indexing at or beyond len panics.
Step 3
Capacity bounds re-slicing
cap(s) is the number of elements from the slice's start to the end of the backing array; you can re-slice up to cap without reallocating.
Step 4
Slicing shares storage
s[low:high] returns a new header into the same array, so writes through one slice are visible through overlapping slices.
Step 5
Append grows when full
If len == cap, append allocates a larger array (often ~2x for small slices), copies elements, and returns a slice over the new array.
Step 6
Preallocate to avoid churn
make([]T, 0, n) reserves capacity so appends up to n avoid repeated reallocation and copying.
What Interviewer Expects
- The 3-field slice header: pointer, length, capacity
- Difference between len() and cap()
- That slices share an underlying array until reallocation
- How append triggers a new backing array when capacity is exceeded
- Awareness of aliasing bugs from overlapping slices
Common Mistakes
- Thinking a slice stores its own copy of the data
- Assuming length and capacity are always equal
- Believing append never affects the original slice
- Not realizing overlapping slices mutate shared memory
- Expecting the backing array to shrink after re-slicing
Best Answer (HR Friendly)
“A Go slice is a lightweight window into a shared block of memory. It remembers where the data starts, how many items it currently holds, and how much room is left before it must grow into a bigger block.”
Code Example
arr := [5]int{10, 20, 30, 40, 50}
s := arr[1:3] // points into arr
fmt.Println(len(s), cap(s)) // 2 4 (start at index 1, room to index 4)
s[0] = 99 // mutates arr[1] too — shared storage
fmt.Println(arr[1]) // 99
s = append(s, 60) // fits within cap, still shares arr
fmt.Println(arr[3]) // 60 — overwrote arr[3]
big := append(s, 70, 80, 90) // exceeds cap -> new array
big[0] = -1 // no longer affects arr
fmt.Println(arr[1]) // still 99Follow-up Questions
- What growth strategy does append use as slices get larger?
- How can overlapping slices cause subtle aliasing bugs?
- What does make([]T, len, cap) do differently from make([]T, len)?
- How do you safely copy a slice so it does not share storage?
- Why can appending to a slice inside a function fail to update the caller's slice?
MCQ Practice
1. What does cap() return for a slice?
Capacity measures how far the slice can be re-sliced or grown before a new backing array is needed — from its start pointer to the end of the underlying array.
2. When does append allocate a new backing array?
append reuses the existing array while there is spare capacity; once len would exceed cap, it allocates a larger array and copies the elements.
3. Two slices created from the same array via slicing will:
Slicing produces new headers into the same array, so writes through overlapping regions are visible to both slices until a reallocation separates them.
Flash Cards
What three fields make up a slice header? — A pointer to the backing array, a length (len), and a capacity (cap).
Difference between len and cap? — len is how many elements are usable now; cap is how many fit from the start pointer to the array's end before growth.
When does append reallocate? — When the new length would exceed the current capacity — it copies to a bigger array and returns a slice over it.
Do sliced slices share memory? — Yes, until a reallocation; writing through one overlapping slice is visible through the other.
How to reserve capacity? — Use make([]T, 0, n) so appends up to n avoid repeated allocation and copying.