How can subslicing cause memory leaks, and how do you fix them?
Question 49HardGo 1.22 to 1.25
A subslice keeps the entire backing array reachable, not just the part you can see. If you read a 10 MB file and keep data[:16] in a long-lived cache, all 10 MB stays pinned. Substrings have the same problem: s[a:b] shares the original string's bytes.
// Leaky: header keeps the whole 10MB array alive
func magic(path string) []byte {
data, _ := os.ReadFile(path)
return data[:4]
}
// Fixed: copy the bytes you need
func magicFixed(path string) []byte {
data, _ := os.ReadFile(path)
return slices.Clone(data[:4]) // or bytes.Clone / append([]byte(nil), ...)
}
// Strings: same problem
func firstToken(line string) string {
tok, _, _ := strings.Cut(line, " ")
return strings.Clone(tok) // Go 1.20: detaches from the big line
}
A related leak: pointers past len. After s = s[:len(s)-1] on a []*Big, the removed element is still in the backing array, so the GC cannot free it. Zero it first:
s[len(s)-1] = nil
s = s[:len(s)-1]
What interviewers look for: knowing that the GC tracks objects (the whole array), not ranges, and knowing to use pprof heap profiles (inuse_space) to find these leaks.
More on Arrays, Slices, Maps & Strings
- Q47How do arrays differ from slices? Can you convert between them?
- Q48What does this print? (range over an array vs a slice)
- Q50How do you delete elements from a slice efficiently, and what changed with slices.Delete in Go 1.22?
- Q51Why can't you compare slices with ==? How do you check equality of slices, maps, and structs containing them?
- Q52How do you correctly allocate a 2D slice, and what are the performance trade-offs?
- Q53Why do the slices package functions use the constraint [S ~[]E, E any] instead of just []E?