Does Range Copy The Slice
Fast recall
Quick revision
Core answer
Practical understanding of Range Over Slice Map.
Repeating a definition without saying when to use it.
Give one trade-off and say how you would test it.
Interview-ready guide
Interview answer
The range expression is evaluated exactly once, at loop start. For a slice, that means the header — pointer, length, capacity — is copied once into the loop's internal state. The elements are not copied; the loop reads through the pointer.
The observable consequence: the loop's length is frozen at entry. Appends inside the body grow a different (new) backing array — the loop keeps walking the original header's extent:
s := []int{1, 2, 3}
for i, v := range s {
s = append(s, v*10) // grows s — the loop doesn't see it
fmt.Println(i, v)
}
fmt.Println(len(s)) // 6 — the appends DID land, after the loop's snapshotThree iterations, not six — the loop captured len 3 at start. This is why range-over-slice can't infinite-loop on self-append the way some for-loops in other languages can.
The same once-only rule explains the nested pattern: ranging s and ranging s[i] inside — each inner range snapshots its own expression, and neither is affected by outer-body changes to s's length. It's consistent: one evaluation, one snapshot, every range.
Does Range Copy The Slice
Direct Answer
range evaluates the slice expression once — copying the header. Length is fixed at loop start: appends inside the loop are invisible to it.
Deep Dive & Architecture
The once-only evaluation is the language rule that makes loop semantics stable: 'The range expression x is evaluated once before beginning the loop.' For a slice, that evaluation yields the header (pointer, len, cap) — and the loop's internal snapshot is that header.
The append-invisibility follows mechanically: appends that fit in capacity write into the SAME backing array — the loop, walking its snapshot pointer, DOES see those in-place writes to elements it hasn't reached. But appends that grow the slice allocate a new array — the loop's snapshot still points at the old one — and those elements are invisible.
So the precise statement is subtler than 'appends are invisible': in-capacity appends to not-yet-visited indices ARE seen (shared storage); growth appends are not (forked storage). Both facts fall out of the header snapshot:
s := make([]int, 3, 10)
for i, v := range s {
s = append(s, 99) // in-capacity: writes backing[3..]
_ = v
fmt.Println(i) // 0 1 2 — the snapshot's len, never more
}
The loop length froze at 3; the shared-array writes happened where the loop never looks. Range can't loop forever on self-append — the frozen len is the safety.
Common Mistakes & Pitfalls
Repeating a definition without saying when to use it.
Check your understanding
Answer these without looking back at the article.
- What is Range Over Slice Map, and when would you use it?
- When would you use Range Over Slice Map in a real project?
- How would you compare Range Over Slice Map with an alternative?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.