A strong answer
In Go, a slice []T is not a pointer to a dynamic object; it is a 24-byte value struct (runtime.slice) copied by value on assignment and function calls, containing three 8-byte fields: ptr (pointer to the first element in an underlying contiguous backing array), len (number of readable elements), and cap (total slots from ptr to the end of the backing array). Slicing b := a[0:2] on a 4-capacity array sets b.len = 2, b.cap = 4 sharing the same ptr! When you call append(b, x) and b.len < b.cap (2 < 4), append does not allocate a new array—it writes x directly into slot 2 of the shared backing array and returns a new 24-byte header with len = 3! To force append to allocate a fresh backing array instead of overwriting shared spare capacity, use a three-index slice expression a[low:high:max] (b := a[0:2:2]), which sets cap = max - low = 2 (len == cap).
Calling append(s, x) mutates the existing backing array at index s.len in-place if and only if s.len < s.cap; if s.len == s.cap (such as after capping capacity via s[i:j:j]), append allocates a new backing array and copies s.len elements without mutating the original backing array. Because a three-index slice b := a[i:j:j] sets b.len = j - i and b.cap = j - i, b has zero spare capacity (b.len == b.cap). Any subsequent append(b, ...) is forced to take the growslice allocation path, leaving a's backing array untouched.
Passing a 1,000,000-element []byte slice to a function copies only 24 bytes (ptr, len, cap) on the stack in ~1 nanosecond—never pass *[]T unless the callee needs to modify the caller's len or cap header without returning it! In production systems (Go runtime/slice.go (growslice), bytes.Clone() / slices.Clip() (which implements s[:len(s):len(s)]), and bufio.Scanner.Bytes() (which reuses the same backing array across tokens).), engineers monitor these exact trade-offs to keep p99 latency predictable and prevent resource exhaustion under peak load.
What the interviewer is listening for
- Traces how Go's 24-byte slice header (
ptr, len, cap) and three-index capacity capping (s[i:j:k]) operates in memory step by step rather than giving a vague textbook definition. - States the core correctness invariant and explains why edge cases do not violate it.
- Quantifies time, space, and hardware costs (Passing
[]Tto a Function, Three-Index Slices[:n:n]/slices.Clip) with concrete numbers. - Identifies real-world failure modes (Storing a tiny slice
id := bigPacket[4:20]in a long-lived map or struct) and explains how production systems mitigate them.
Common mistakes
- Treating Go's 24-byte slice header (
ptr, len, cap) and three-index capacity capping (s[i:j:k]) as a black box without explaining the underlying memory or state transitions. - Ignoring worst-case tail latency or memory overhead (Branching two
appendcalls off a shared prefix slicebasethat haslen < cap). - Forgetting boundary conditions and error recovery (Appending 1,000,000 items to
var s []Twithout pre-sizing whennis known).