Skip to content

Go Pointers with Structs — Find the Bug

Instructions

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


Bug 1 🟢 — Mixing Receivers Breaks Interface

type Animal struct{ Name string }
func (a Animal) Name1() string  { return a.Name }
func (a *Animal) Bark()         { fmt.Println(a.Name) }

type Speaker interface { Name1() string; Bark() }

var s Speaker = Animal{Name: "Rex"}
Solution **Bug**: `Animal{}` doesn't satisfy `Speaker` because `Bark` has pointer receiver. Compile error. **Fix**: `var s Speaker = &Animal{Name: "Rex"}` — `*Animal` has both methods. **Lesson**: Pick consistent receiver types. Mixed receivers limit interface satisfaction.

Bug 2 🟢 — Nil Struct Field Access

var p *Point
fmt.Println(p.X)
Solution **Bug**: Nil pointer dereference panic. Auto-dereferencing nil panics. **Fix**:
if p != nil { fmt.Println(p.X) }

Bug 3 🟢 — Map Value Mutation

type Counter struct{ N int }
m := map[string]Counter{"a": {N: 1}}
m["a"].N++
Solution **Bug**: Map values are not addressable; can't mutate fields directly. Compile error. **Fix** (extract):
c := m["a"]; c.N++; m["a"] = c
Or store pointers:
m := map[string]*Counter{"a": {N: 1}}
m["a"].N++

Bug 4 🟡 — Returning Slice of Pointers from Loop

type Item struct{ V int }
func make() []*Item {
    items := []Item{{1}, {2}, {3}}
    var ptrs []*Item
    for _, it := range items {
        ptrs = append(ptrs, &it) // pre-1.22 bug
    }
    return ptrs
}

(Pre Go 1.22.) What does each pointer point to?

Solution **Pre-1.22**: All pointers point to the same `it` (last value: `{V: 3}`). **Fix** for pre-1.22:
for i := range items {
    ptrs = append(ptrs, &items[i]) // index — different memory
}
Or shadow with `it := it` (creates per-iter copy). **Lesson**: Pre-1.22 loop variable capture pitfall. Use index for slice element pointers.

Bug 5 🟡 — Pointer Receiver Required on Non-Addressable Value

type T struct{ N int }
func (t *T) Inc() { t.N++ }

func main() {
    T{N: 1}.Inc() // ?
}
Solution **Bug**: `T{N: 1}` is a composite literal, not addressable. Can't call pointer-receiver method. Compile error. **Fix** — assign to a variable first:
t := T{N: 1}
t.Inc() // OK; Go takes &t
Or allocate with pointer:
(&T{N: 1}).Inc() // OK

Bug 6 🟡 — Constructor Returns Pointer to Local That's Reused

type Builder struct{ Items []int }
var b Builder

func New() *Builder {
    b = Builder{Items: []int{}} // reuses package var
    return &b
}
Solution **Bug**: All callers get a pointer to the SAME `b`. Each `New()` overwrites the previous; old "instances" become garbage. **Fix** — fresh allocation each call:
func New() *Builder {
    return &Builder{Items: []int{}}
}
**Lesson**: Constructors should allocate fresh per call.

Bug 7 🟡 — Storing Pointer to Local

type Service struct{ Logger *Logger }

func setup() *Service {
    l := Logger{Prefix: "SVC"}
    return &Service{Logger: &l}
}

Is this safe?

Solution **Discussion**: Yes — Go's escape analysis moves `l` to the heap because `&l` escapes. Safe. The compiler verifies; this is idiomatic Go. Verify with `go build -gcflags="-m"`. **Lesson**: Returning pointers to locals is safe in Go (unlike C); escape analysis handles allocation.

Bug 8 🔴 — Concurrent Mutation of Pointer Field

type Cache struct{ data *Data }

func update(c *Cache, d *Data) { c.data = d }

c := &Cache{}
go update(c, &Data{V: 1})
go update(c, &Data{V: 2})
fmt.Println(c.data)
Solution **Bug**: Two goroutines write to `c.data` concurrently. Race. Reader may see torn value. **Fix** — atomic.Pointer:
import "sync/atomic"

type Cache struct{ data atomic.Pointer[Data] }

func update(c *Cache, d *Data) { c.data.Store(d) }
Or mutex. **Lesson**: Concurrent pointer-field updates need atomic or mutex.

Bug 9 🔴 — Embedded Pointer Method Conflict

type A struct{}
func (a *A) M() string { return "A" }

type B struct{}
func (b *B) M() string { return "B" }

type C struct {
    *A
    *B
}

c := &C{A: &A{}, B: &B{}}
c.M() // ?
Solution **Bug**: Both A and B have method M; ambiguous. Compile error: `ambiguous selector c.M`. **Fix** — be explicit:
c.A.M() // "A"
c.B.M() // "B"
Or define `M` on C to disambiguate:
func (c *C) M() string { return c.A.M() + "+" + c.B.M() }
**Lesson**: Embedded pointer fields with overlapping methods need explicit selection.

Bug 10 🔴 — Defensive Copy Missing

type Buffer struct{ data []int }

func New(initial []int) *Buffer {
    return &Buffer{data: initial} // BUG: aliases caller
}

func main() {
    src := []int{1, 2, 3}
    b := New(src)
    src[0] = 99
    fmt.Println(b.data) // expected [1 2 3]
}
Solution **Bug**: `b.data` aliases `src`. Mutating src affects b. **Fix** — defensive copy:
func New(initial []int) *Buffer {
    return &Buffer{data: append([]int(nil), initial...)}
}
**Lesson**: Constructors taking slices/maps should defensively copy if the caller's data shouldn't be aliased.