Skip to content

Go Pointers with Maps & Slices — Find the Bug

Instructions

Identify, explain, fix. Difficulty: 🟢 🟡 🔴.


Bug 1 🟢 — &m["key"]

m := map[string]int{"a": 1}
p := &m["a"]
*p = 99
Solution **Bug**: Map values not addressable. Compile error. **Fix**: extract or use `map[K]*V`:
v := m["a"]; v = 99; m["a"] = v

Bug 2 🟢 — Append Result Discarded

s := []int{1, 2, 3}
append(s, 99)
fmt.Println(s) // expected [1 2 3 99]
Solution **Bug**: `append` returns a new slice; discarding the result loses the change. Output: `[1 2 3]`. **Fix**: `s = append(s, 99)`.

Bug 3 🟢 — Map Value Field Mutation

type Counter struct{ N int }
m := map[string]Counter{"a": {N: 1}}
m["a"].N++
Solution **Bug**: Map value field can't be modified directly (not addressable). Compile error. **Fix**: store as `map[string]*Counter` OR extract-modify-restore.

Bug 4 🟡 — Stale Pointer After Append

s := make([]int, 3, 3)
s[0] = 1
p := &s[0]
s = append(s, 99)
*p = 999
fmt.Println(s)
Solution **Bug**: append reallocates (cap was 3, exceeded). `p` points to the old (detached) array. `*p = 999` doesn't affect `s`. Output: `[1 0 0 99]`. **Fix**: use index `s[0] = 999` instead of cached pointer; or `p = &s[0]` after append.

Bug 5 🟡 — Subslice Append Surprise

a := []int{1, 2, 3, 4, 5}
b := a[:3] // cap=5
b = append(b, 99)
fmt.Println(a) // expected [1 2 3 4 5]
Solution **Bug**: `b`'s cap is 5 (inherited from `a`). `append` writes within shared backing — overwrites `a[3]`. Output: `[1 2 3 99 5]`. **Fix** (defensive copy):
b := append([]int{}, a[:3]...)
b = append(b, 99)
// a is unaffected

Bug 6 🟡 — Concurrent Map Panic

m := map[int]int{}
for i := 0; i < 100; i++ {
    go func(i int) { m[i] = i }(i)
}
Solution **Bug**: Concurrent map writes panic with "concurrent map writes". **Fix**: `sync.RWMutex`:
var mu sync.RWMutex
m := map[int]int{}
for i := 0; i < 100; i++ {
    go func(i int) {
        mu.Lock()
        m[i] = i
        mu.Unlock()
    }(i)
}
Or `sync.Map`.

Bug 7 🟡 — Loop Variable Pointer in Pre-1.22

items := []int{1, 2, 3}
var ptrs []*int
for _, x := range items {
    ptrs = append(ptrs, &x)
}
Solution **Pre-1.22**: All `&x` are the same pointer. After loop, all point to value 3. **Fix**: shadow `x := x`, OR use index `&items[i]`, OR upgrade to Go 1.22+.

Bug 8 🔴 — Subslice Pinning Memory

big := make([]byte, 1<<20)
small := big[:10]
big = nil
runtime.GC()
// 1 MB still alive (small references the backing)
Solution **Bug**: `small` references the backing array. The whole 1 MB stays alive. **Fix** — copy out:
small := make([]byte, 10)
copy(small, big[:10])
big = nil
// 1 MB collectable

Bug 9 🔴 — Map Iteration Order Assumption

m := map[string]int{"a": 1, "b": 2, "c": 3}
for k, v := range m {
    fmt.Println(k, v)
}
// Code assumes alphabetical order
Solution **Bug**: Map iteration order is RANDOMIZED by Go (intentionally, to discourage reliance on order). Output varies. **Fix** — sort keys explicitly:
keys := make([]string, 0, len(m))
for k := range m { keys = append(keys, k) }
sort.Strings(keys)
for _, k := range keys {
    fmt.Println(k, m[k])
}

Bug 10 🔴 — Storing Pointers to Loop's Slice Index

items := []int{1, 2, 3}
var ptrs []*int
for i := range items {
    ptrs = append(ptrs, &items[i])
}
items = append(items, 99) // may realloc
fmt.Println(*ptrs[0])
Solution **Bug**: After append, `items` may have a new backing array. `ptrs[0]` still points to the old array. **Fix**: don't append after taking element pointers, OR re-acquire pointers after append, OR use indices instead of pointers.