Fix: Go panic: runtime error: invalid memory address or nil pointer dereference
Quick Answer
How to fix Go nil pointer dereference panics — checking for nil before use, nil interface traps, nil map writes, receiver methods on nil, and defensive nil handling patterns.
The Error
Your Go program panics with:
goroutine 1 [running]:
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x...]
goroutine 1 [running]:
main.getUsername(...)
/app/main.go:23 +0x18
main.main()
/app/main.go:15 +0x28
exit status 2Or you see it in an HTTP server that suddenly crashes:
2026/03/19 10:23:45 http: panic serving 127.0.0.1:51234:
runtime error: invalid memory address or nil pointer dereferenceThe stack trace points to a line where you’re accessing a field or calling a method on a pointer that is nil.
Why This Happens
In Go, a pointer, interface, map, slice, channel, or function value that hasn’t been initialized is nil. Accessing a field or calling a method on nil causes a panic at runtime:
- Uninitialized pointer —
var p *MyStructdeclares a nil pointer. Accessingp.Fieldpanics. - Function returning nil on error path — a function returns
(*MyStruct, error)and you ignore the error, then dereference the nil pointer. - Nil interface — an interface variable containing a nil concrete value behaves differently from a true nil interface. Calling methods on it panics.
- Nil map write —
var m map[string]int; m["key"] = 1panics. Maps must be initialized withmake. - Nil receiver — calling a method on a nil pointer receiver panics unless the method explicitly handles nil.
- Chained nil access —
user.Address.Citypanics ifuser.Addressis nil, even ifuseris not nil.
Fix 1: Check for nil Before Dereferencing
Always check pointers for nil before accessing their fields:
// PANICS if user is nil
func getUsername(user *User) string {
return user.Name // panic: nil pointer dereference
}
// SAFE
func getUsername(user *User) string {
if user == nil {
return "" // or return a default, or return an error
}
return user.Name
}For nested structs, check each level:
type User struct {
Name string
Address *Address
}
type Address struct {
City string
}
// PANICS if user.Address is nil
func getCity(user *User) string {
return user.Address.City
}
// SAFE
func getCity(user *User) string {
if user == nil || user.Address == nil {
return ""
}
return user.Address.City
}Fix 2: Always Check Errors from Functions That Return Pointers
The most common source of nil pointer panics is ignoring error return values:
// PANICS — user is nil when FindUser returns an error
user, _ := db.FindUser(id)
fmt.Println(user.Name) // panic if user is nil
// SAFE — check the error before using the result
user, err := db.FindUser(id)
if err != nil {
return fmt.Errorf("finding user %d: %w", id, err)
}
fmt.Println(user.Name) // Safe — user is not nil if err is nilThe contract in Go: if a function returns (*T, error), it should return either a valid pointer with nil error, or nil pointer with a non-nil error. Don’t trust the pointer until you’ve verified the error is nil.
// Function that follows the convention
func FindUser(id int) (*User, error) {
var user User
err := db.QueryRow("SELECT * FROM users WHERE id = $1", id).Scan(&user.ID, &user.Name)
if err == sql.ErrNoRows {
return nil, fmt.Errorf("user %d not found", id)
}
if err != nil {
return nil, fmt.Errorf("querying user: %w", err)
}
return &user, nil
}Common Mistake: Using
_to discard errors from functions that return pointers. Any_, _orval, _pattern that discards the error is a potential nil panic waiting to happen.
Fix 3: Initialize Maps Before Writing
A nil map causes a panic on write but not on read (reads return the zero value):
var scores map[string]int
// READ — safe, returns 0 (zero value for int)
val := scores["alice"] // val = 0, no panic
// WRITE — PANICS
scores["alice"] = 100 // panic: assignment to entry in nil mapFix — initialize the map:
// Option 1: make
scores := make(map[string]int)
scores["alice"] = 100 // Safe
// Option 2: map literal
scores := map[string]int{}
scores["alice"] = 100 // Safe
// Option 3: map literal with initial values
scores := map[string]int{
"alice": 100,
"bob": 85,
}In structs, initialize maps in a constructor:
type UserStore struct {
users map[string]*User
}
// Without constructor — users is nil
var store UserStore
store.users["alice"] = &User{} // panic
// With constructor
func NewUserStore() *UserStore {
return &UserStore{
users: make(map[string]*User), // Initialize the map
}
}
store := NewUserStore()
store.users["alice"] = &User{} // SafeFix 4: Handle the Nil Interface Trap
A nil interface and an interface holding a nil pointer are different:
type Animal interface {
Sound() string
}
type Dog struct{}
func (d *Dog) Sound() string { return "woof" }
var dog *Dog = nil
var animal Animal = dog // Interface holds a nil *Dog
// animal is NOT nil — it's an interface with a non-nil type but nil value
fmt.Println(animal == nil) // false!
animal.Sound() // panic: nil pointer dereference (inside Sound())This trap is most common when returning interfaces:
// BUG — returns a non-nil interface holding a nil pointer
func getAnimal(sound string) Animal {
var dog *Dog
if sound == "woof" {
dog = &Dog{}
}
return dog // If dog is nil, this returns a non-nil interface!
}
animal := getAnimal("meow")
if animal != nil { // This check passes! animal is not nil interface
animal.Sound() // PANIC
}
// FIX — return nil interface explicitly
func getAnimal(sound string) Animal {
if sound == "woof" {
return &Dog{}
}
return nil // Returns a true nil interface
}Fix 5: Handle nil Receivers in Methods
A method can be called on a nil receiver if it’s a pointer receiver. If the method doesn’t check for nil, it panics when accessing fields:
type Node struct {
Value int
Next *Node
}
// PANICS if n is nil
func (n *Node) GetValue() int {
return n.Value // panic if n == nil
}
// SAFE — explicitly handle nil receiver
func (n *Node) GetValue() int {
if n == nil {
return 0
}
return n.Value
}
// This pattern allows safe traversal of nil-terminated lists
func (n *Node) String() string {
if n == nil {
return "nil"
}
return fmt.Sprintf("%d -> %s", n.Value, n.Next.String())
}This technique is used in the standard library (e.g., (*bytes.Buffer).Write handles nil safely).
Fix 6: Use the ok Pattern for Type Assertions
Type assertions on interface values panic if the interface is nil or the type doesn’t match:
var i interface{} = nil
// PANICS
s := i.(string) // panic: interface conversion: interface is nil, not string
// SAFE — two-value form
s, ok := i.(string)
if !ok {
fmt.Println("not a string")
return
}
fmt.Println(s)Same for map lookups — always use the ok form when existence matters:
m := map[string]*User{"alice": {Name: "Alice"}}
// RISKY — user might be nil if key doesn't exist
user := m["bob"]
fmt.Println(user.Name) // panic — "bob" not in map
// SAFE
user, ok := m["bob"]
if !ok {
fmt.Println("user not found")
return
}
fmt.Println(user.Name)Fix 7: Recover from Panics in HTTP Handlers
For HTTP servers, a nil pointer panic in one handler shouldn’t crash the entire server. Use recover in middleware:
func recoveryMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Printf("panic: %v\n%s", err, debug.Stack())
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/api/users", handleUsers)
// Wrap with recovery middleware
handler := recoveryMiddleware(mux)
log.Fatal(http.ListenAndServe(":8080", handler))
}Fix the underlying nil access — recover is a safety net, not a solution. Find and fix the cause by reading the stack trace in the log.
Debugging nil Pointer Panics
Read the full stack trace. The panic output shows the exact line number and call chain:
goroutine 1 [running]:
main.getCity(...)
/app/main.go:23 +0x18 ← Line 23 in main.go
main.handleRequest(...)
/app/main.go:15 +0x28Go to line 23. The dereference is there. Ask: “Which pointer on this line could be nil?”
Add nil checks progressively:
func getCity(user *User) string {
fmt.Printf("user: %v\n", user) // Debug
fmt.Printf("user.Address: %v\n", user.Address) // Debug
return user.Address.City
}Use dlv (Delve debugger) to catch panics:
dlv debug ./main.go
(dlv) catch panic
(dlv) continue
# Execution pauses at the panic with full state inspectionStill Not Working?
Check for concurrent nil writes. If multiple goroutines write to a pointer without synchronization, one goroutine might set it to nil while another reads it:
var user *User
go func() {
user = nil // Concurrent nil write
}()
if user != nil {
fmt.Println(user.Name) // Race condition: user might be nil here
}Fix with a mutex or use sync/atomic for pointer operations.
Use go vet and the race detector:
go vet ./...
go test -race ./...
go run -race main.goThe race detector catches concurrent nil pointer races that are otherwise intermittent and hard to reproduce.
For related Go issues, see Fix: Go Goroutine Deadlock and Fix: Go panic: runtime error: index out of range.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Go context deadline exceeded / context canceled
How to fix Go context.DeadlineExceeded and context.Canceled errors — setting timeouts correctly, propagating context through call chains, handling cancellation, and debugging which operation timed out.
Fix: Go Test Not Working — Tests Not Running, Failing Unexpectedly, or Coverage Not Collected
How to fix Go testing issues — test function naming, table-driven tests, t.Run subtests, httptest, testify assertions, and common go test flag errors.
Fix: Go Generics Type Constraint Error — Does Not Implement or Cannot Use as Type
How to fix Go generics errors — type constraints, interface vs constraint, comparable, union types, type inference failures, and common generic function pitfalls.
Fix: Go Deadlock — all goroutines are asleep, deadlock!
How to fix Go channel deadlocks — unbuffered vs buffered channels, missing goroutines, select statements, closing channels, sync primitives, and detecting deadlocks with go race detector.