Under The Hood
backend go Go 1.21+

Go slices: the three-word header

Last updated
Prerequisites:
Go basics: arrays, slices, functions
Understanding of value semantics vs reference semantics
  • go
  • slices
  • memory
  • arrays
  • aliasing
  • append

Read at your depth

The practical view

A slice is a struct of three words: a pointer to the underlying array, len (length: how many elements are valid), and cap (capacity: how many fit before reallocation). s := make([]int, 3, 5) has len 3, cap 5. append(s, v) writes at s[len] if len < cap; when len == cap, it allocates a new array (usually doubling capacity), copies, and returns a NEW slice header — which is why you must assign the result: s = append(s, v). Slices are passed by value (the header is copied) but share the underlying array, so mutating elements inside a function is visible to the caller.

Legacy vs modern

Aliasing append into a sub-slice vs explicit capacity control

A shared backing array turns a local append into a caller-visible mutation; the full slice expression or a fresh copy restores isolation.

before → after
Shared backing array
a := make([]int, 2, 4) // len 2, cap 4
b := a[:2]
b = append(b, 9)     // writes INTO a's array
Isolated copy
b := append([]T(nil), a[:n]...) // fresh backing array

Interview gotchas

Context

This is the classic Go aliasing question — the interviewer wants the len/cap condition that decides between in-place write and reallocation.

The mechanical answer

append writes the new element at s[len] and increments the header's len — no copy — whenever len < cap. Because the header shares the backing array, any other slice pointing into that array observes the write. When len == cap, append calls growslice: it allocates a new, larger array, copies all elements, and returns a new header pointing to the new array — the original header (and any others sharing the old array) are untouched. So whether 'the original' changes depends on whether the append stayed within the shared capacity. The full slice expression s[low:high:max] caps future appends by setting an explicit max capacity.

Trap

The naive answer is 'append copies the array'. It only copies on capacity exhaustion; most appends are in-place writes into shared memory. Another trap: 'slices are passed by reference' — the header is passed by value; only the backing array is shared. Candidates who demonstrate the len<cap branch versus the grow branch, and name the full slice expression as the isolation tool, nail this question.

Under The Hood — a multi-depth technical interview hub.

Third-party licenses Press ⌘ K to search.