for range — Find the Bug¶
Each section contains buggy Go code. Try to identify the bug before opening the solution.
Bug 1 🟢 — Modifying the Value Variable¶
package main
import "fmt"
func double(s []int) {
for _, v := range s {
v *= 2
}
}
func main() {
nums := []int{1, 2, 3}
double(nums)
fmt.Println(nums) // expected [2 4 6]
}
What is wrong?
Solution
`v` is a copy of each element. Modifying `v` does not affect the original slice. **Fix:** Output is now `[2 4 6]`.Bug 2 🟢 — Map Iteration Order Assumed¶
package main
import "fmt"
func main() {
m := map[string]int{"c": 3, "a": 1, "b": 2}
result := []string{}
for k := range m {
result = append(result, k)
}
fmt.Println(result) // expected ["a", "b", "c"]
}
What is wrong?
Solution
Map iteration in Go is deliberately randomized. The output may be `[c a b]`, `[b c a]`, or any permutation. **Fix (if sorted order is needed):**Bug 3 🟢 — Range over Array (Copies Entire Array)¶
package main
import "fmt"
func sumArray(arr [1000000]int) int {
sum := 0
for _, v := range arr {
sum += v
}
return sum
}
func main() {
var big [1000000]int
for i := range big { big[i] = i }
fmt.Println(sumArray(big))
}
What is wrong?
Solution
The array `arr` is passed **by value** to `sumArray`, which copies 8MB on the stack/heap. Then `for range arr` copies it AGAIN (range expression captures the array). **Fix:** Pass a slice (or pointer to array):Bug 4 🟡 — Closure Capture in Goroutines¶
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
results := make([]int, 5)
for i, v := range []int{1, 2, 3, 4, 5} {
wg.Add(1)
go func() {
defer wg.Done()
results[i] = v * 2
}()
}
wg.Wait()
fmt.Println(results)
}
What is wrong? (Pre-Go 1.22)
Solution
Both `i` and `v` are captured by reference in the goroutine closure. By the time goroutines execute, the loop may have finished, so `i` and `v` hold the last iteration's values (4 and 5). All goroutines write to `results[4] = 10`. **Fix:**Bug 5 🟡 — Defer in Loop¶
package main
import (
"fmt"
"os"
)
func processAll(filenames []string) error {
for _, name := range filenames {
f, err := os.Open(name)
if err != nil {
return err
}
defer f.Close() // intended to close after each file
data := make([]byte, 1024)
f.Read(data)
fmt.Println(string(data))
}
return nil
}
What is wrong?
Solution
`defer f.Close()` does NOT run at the end of each loop iteration. All defers run when `processAll` returns. If `filenames` is large, you'll have many open file descriptors, potentially exhausting the OS limit. **Fix:**func processAll(filenames []string) error {
for _, name := range filenames {
if err := processFile(name); err != nil {
return err
}
}
return nil
}
func processFile(name string) error {
f, err := os.Open(name)
if err != nil {
return err
}
defer f.Close() // runs when processFile returns — correct!
data := make([]byte, 1024)
f.Read(data)
fmt.Println(string(data))
return nil
}
Bug 6 🟡 — String Index vs Rune Index¶
package main
import "fmt"
func getNthChar(s string, n int) string {
return string(s[n]) // get the nth character
}
func main() {
s := "Hello, 世界"
fmt.Println(getNthChar(s, 7)) // expected: 世
}
What is wrong?
Solution
`s[7]` returns the **byte** at position 7, not the 7th character. For the string `"Hello, 世界"`, byte 7 is the second byte of `世` (0xb8), not a valid character boundary. **Fix:**Bug 7 🟡 — Concurrent Map Write During Range¶
package main
import (
"fmt"
"sync"
)
var cache = map[string]int{}
func readCache(wg *sync.WaitGroup) {
defer wg.Done()
for k, v := range cache {
fmt.Println(k, v)
}
}
func writeCache(wg *sync.WaitGroup, key string, val int) {
defer wg.Done()
cache[key] = val
}
func main() {
var wg sync.WaitGroup
cache["a"] = 1
cache["b"] = 2
wg.Add(2)
go readCache(&wg)
go writeCache(&wg, "c", 3)
wg.Wait()
}
What is wrong?
Solution
Concurrent map read (range) and write causes a fatal runtime panic: `concurrent map iteration and map write`. Go's map is not goroutine-safe. **Fix:**var mu sync.RWMutex
var cache = map[string]int{}
func readCache(wg *sync.WaitGroup) {
defer wg.Done()
mu.RLock()
defer mu.RUnlock()
for k, v := range cache {
fmt.Println(k, v)
}
}
func writeCache(wg *sync.WaitGroup, key string, val int) {
defer wg.Done()
mu.Lock()
defer mu.Unlock()
cache[key] = val
}
Bug 8 🔴 — Pointer to Range Variable¶
package main
import "fmt"
func getPtrs(s []int) []*int {
ptrs := make([]*int, len(s))
for i, v := range s {
ptrs[i] = &v // store pointer to range variable
}
return ptrs
}
func main() {
ptrs := getPtrs([]int{10, 20, 30})
for _, p := range ptrs {
fmt.Println(*p) // expected: 10, 20, 30
}
}
What is wrong? (Pre-Go 1.22)
Solution
In pre-Go 1.22, `v` is a single variable reused each iteration. All `ptrs[i]` point to the SAME variable `v`, which holds `30` (the last value) after the loop ends. Output: `30 30 30`. **Fix:**func getPtrs(s []int) []*int {
ptrs := make([]*int, len(s))
for i, v := range s {
v := v // create new variable per iteration
ptrs[i] = &v
}
return ptrs
}
// Or store address of original elements:
func getPtrs(s []int) []*int {
ptrs := make([]*int, len(s))
for i := range s {
ptrs[i] = &s[i] // address of actual slice element
}
return ptrs
}
Bug 9 🔴 — Modifying Map During Range (Adding Keys)¶
package main
import "fmt"
func expandMap(m map[int]int) {
for k, v := range m {
m[k*10] = v * 10 // add derived keys
}
}
func main() {
m := map[int]int{1: 1, 2: 2, 3: 3}
expandMap(m)
fmt.Println(m) // expected: {1:1, 2:2, 3:3, 10:10, 20:20, 30:30}
}
What is wrong?
Solution
When you add keys to a map during range iteration, the Go spec says the new keys may or may not be iterated. The function is also likely to iterate the newly added keys (like 10, 20, 30) and add 100, 200, 300, causing an unbounded expansion or at least unpredictable behavior. **Fix:** Collect new entries first, then add them:Bug 10 🔴 — Channel Range Without Close¶
package main
import "fmt"
func producer(ch chan<- int, n int) {
for i := 0; i < n; i++ {
ch <- i
}
// forgot to close(ch)
}
func main() {
ch := make(chan int, 10)
go producer(ch, 5)
for v := range ch { // blocks forever after receiving 5 values
fmt.Println(v)
}
fmt.Println("done") // never reached
}
What is wrong?
Solution
`for range` over a channel exits ONLY when the channel is closed. Without `close(ch)`, the range loop blocks forever after receiving all 5 values, causing a goroutine leak (or deadlock if the receiver is main). **Fix:**Bug 11 🔴 — Range Over Interface Slice with Type Mutation¶
package main
import "fmt"
type Counter struct {
Count int
}
func incrementAll(items []interface{}) {
for _, item := range items {
if c, ok := item.(*Counter); ok {
c.Count++
}
}
}
func main() {
c1 := Counter{Count: 1}
c2 := Counter{Count: 2}
items := []interface{}{c1, c2} // storing VALUES, not pointers
incrementAll(items)
fmt.Println(c1.Count) // expected 2, but prints 1
fmt.Println(c2.Count) // expected 3, but prints 2
}
What is wrong?
Solution
`items` stores `Counter` values (not pointers). When stored as `interface{}`, the type assertion `item.(*Counter)` fails because the interface holds a `Counter` value, not `*Counter`. The `ok` check is false, nothing is incremented. **Fix:** Store pointers:Bug 12 🔴 — Rune Index vs Byte Index in Slice Operation¶
package main
import "fmt"
func firstN(s string, n int) string {
count := 0
for i := range s {
if count == n {
return s[:i] // take bytes up to byte position i
}
count++
}
return s
}
func main() {
fmt.Println(firstN("Hello, 世界", 7)) // expected first 7 runes: "Hello, "
}
What is wrong?