You model a user whose profile hasn't loaded yet as a nil pointer field — reasonable, since not every user has filled theirs in. A method reads straight through it, u.Profile.Bio, and it works great in every test, because every test user has a profile. Then a real signup with no profile yet hits the same code path in production, and the whole request dies.
type User struct { Profile *Profile }
func (u *User) Bio() string {
return u.Profile.Bio // Profile is nil — this panics
}
u := &User{}
fmt.Println(u.Bio())
// panic: runtime error: invalid memory address or nil pointer dereferenceThe last section on this page names the rule; its last code block is the fix, a nil check before the dereference ever happens.
A pointer is a value that holds the memory address of another value. The & operator takes the address of a variable, producing a pointer; the * operator, applied to a pointer, dereferences it to get (or set) the value it points to. Unlike C, Go does not allow arithmetic on pointers (no ptr++), which removes an entire class of memory-corruption bugs while keeping the ability to share and mutate data across function calls.
& and *: address-of and dereference
&x produces a pointer to x, of type *T if x is type T. Given a pointer, *p reads or writes the value it points to.
x := 10
p := &x // p is a *int, holding x's address
fmt.Println(*p) // 10 — dereference to read the value
*p = 20 // dereference to write — this changes x too
fmt.Println(x) // 20Why pass a pointer to a function
Go passes arguments by value — a function normally receives a copy. Passing a pointer instead lets a function mutate the caller's original data, and avoids copying large structs on every call.
func double(n int) { // receives a COPY
n *= 2
}
func doublePtr(n *int) { // receives the ADDRESS
*n *= 2
}
x := 5
double(x)
fmt.Println(x) // still 5 — double() only changed its copy
doublePtr(&x)
fmt.Println(x) // 10 — doublePtr() changed the originalNo pointer arithmetic, and nil pointers
C lets you increment a pointer to walk through memory (ptr++); Go does not allow this at all — it's a compile error. This single restriction eliminates buffer overruns and a large share of classic C memory bugs. A pointer's zero value is nil, meaning it points to nothing; dereferencing a nil pointer (*p when p == nil) causes a runtime panic, so code that receives a pointer typically checks for nil before dereferencing it.
Which is the fix for the Bio() panic at the top of this page — check before the dereference, and return a sensible default instead of crashing:
func (u *User) Bio() string {
if u.Profile == nil {
return ""
}
return u.Profile.Bio
}
u := &User{}
fmt.Printf("%q\n", u.Bio()) // "" — no panic