Range Over A Slice Give You, And
Fast recall
Quick revision
Core answer
Whether you understand Range Over Slice Map beyond memorized syntax.
Giving a definition without explaining a practical trade-off.
Connect the concept to a small production example and state its complexity or failure mode.
Interview-ready guide
Interview answer
The loop gives you two things per iteration: the position and a copy of the element at that position. The copy is the trap — anything you do to it stays done to the copy only.
type Task struct{ Done bool }
tasks := []Task{{false}, {false}}
for _, t := range tasks {
t.Done = true // modifies the COPY
}
fmt.Println(tasks[0].Done) // false — nothing changedThe fix is to use the index and write back through it:
for i := range tasks {
tasks[i].Done = true // writes through to the slice's storage
}or range over a slice of pointers, where the copy is the pointer — pointing at the same underlying struct, so field writes land.
The _ in the buggy version is where the tell lives: by discarding the index you lose the only write-back handle. for i := range s with s[i] is the pattern that can't fail this way — same simplicity, no copy hazard.
Range Over A Slice Give You, And
Direct Answer
Index and a COPY of the element. Modifying the copy doesn't touch the slice — the trap: for _, v := range s { v.X = 1 } silently does nothing.
Deep Dive & Architecture
Why does range copy the element? Because the loop variable is an ordinary variable, and range's assignment into it is ordinary assignment — for value types (structs, ints, strings), assignment copies. The loop var is written fresh each iteration from the slot, and your body writes to that fresh copy, not the slot.
Watch the trap with a struct slice (executed):
type Task struct{ Done bool }
tasks := []Task{{false}, {false}}
for _, t := range tasks {
t.Done = true
}
fmt.Println(tasks[0].Done) // false — the loop never touched the slice
The write landed in t — a stack variable the runtime reuses per iteration — and the slice's backing array never saw it. Contrast the fix, for i := range tasks { tasks[i].Done = true }: here there's no element copy at all (you use the index), and the write goes through the slice's storage directly.
The pointer-slice case explains the boundary: []*Task ranges with the copy being the POINTER — 8 bytes, pointing at the same Task. Field writes through the copy land on the shared struct. So 'range copies the element' is precisely true, and for pointers the copy is a reference — the aliasing you want.
Go 1.22 note: the loop variable is now fresh per iteration (the capture-bug fix), but it is STILL a copy of the element for value types — the two changes are orthogonal, and the copy-trap survives.
Common Mistakes & Pitfalls
Giving a definition without explaining a practical trade-off.
Check your understanding
Answer these without looking back at the article.
- When would you use Range Over Slice Map in a real project?
- How would you compare Range Over Slice Map with an alternative?
- What is a common mistake when using Range Over Slice Map?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.