How Do I Debug Nil Pointer Dereference in Go?

Type: Software Reference Confidence: 0.95 Sources: 7 Verified: 2026-02-20 Freshness: stable

TL;DR

Constraints

Quick Reference

# Cause Likelihood Signature Fix
1 Uninitialized pointer variable ~30% var p *MyStruct then p.Field Initialize: p := &MyStruct{} or p := new(MyStruct) [src2]
2 Function returns nil pointer (error not checked) ~25% result := getUser(id) then result.Name Check: if result == nil { return err } [src3, src5]
3 Nil map write ~15% var m map[string]int then m["key"] = 1 Initialize: m := make(map[string]int) [src1]
4 Interface with nil concrete value ~10% var p *MyError = nil; var err error = p; err != nil is true Return plain nil, not typed nil [src6]
5 Uninitialized struct pointer field ~8% type S struct { Inner *T }; s := S{}; s.Inner.Field Use constructor: NewS() that initializes all fields [src7]
6 Nil method receiver ~5% Calling method on nil pointer receiver Add nil guard: if p == nil { return zero } [src4]
7 Nil function value ~3% var fn func(); fn() Check: if fn != nil { fn() } [src1]
8 Race condition sets pointer to nil ~2% Intermittent panic under load Add sync.Mutex around pointer access [src3]
9 Nil channel operations ~2% var ch chan int; ch <- 1 (blocks) or close(ch) (panics) Initialize: ch := make(chan int) [src1]

Decision Tree

START — panic: runtime error: invalid memory address or nil pointer dereference
├── Read stack trace → identify exact file:line
│   └── Which variable is being dereferenced on that line?
│
├── Is it a struct pointer variable?
│   ├── Declared with `var p *T`? → Initialize: `p = &T{}` [src2]
│   ├── Returned from a function? → Check error: `if err != nil` [src3, src5]
│   └── Is it a struct field? → Initialize in constructor [src7]
│
├── Is it a map?
│   ├── Writing to nil map? → `m = make(map[K]V)` [src1]
│   └── Reading from nil map? → Safe (returns zero value), not the cause
│
├── Is it an interface?
│   ├── Interface itself is nil? → Initialize or check before calling methods
│   └── Non-nil but holds nil value? → Return plain `nil` [src6]
│
├── Is it a function value?
│   └── `var fn func()`? → Check: `if fn != nil { fn() }` [src1]
│
├── Is it intermittent / under concurrent load?
│   └── Race condition → sync.Mutex; `go run -race` [src3]
│
└── Still unclear?
    ├── Add fmt.Printf before panic line [src3]
    ├── Use Delve: `dlv debug`, breakpoint, inspect [src4]
    └── Run `go vet ./...` and `staticcheck ./...` [src4]

Step-by-Step Guide

1. Read the stack trace to find the panic location

Every nil pointer panic prints a goroutine stack trace. The first line after panic: tells you the file and line number. [src3]

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x1092a9d]

goroutine 1 [running]:
main.processUser(0x0)
        /app/main.go:25 +0x1d      <-- file main.go, line 25
main.main()
        /app/main.go:12 +0x45

Key: addr=0x0 confirms nil dereference. (0x0) in function args shows the pointer was nil.

2. Identify which pointer is nil on that line

Look for field access (p.Field), method call (p.Method()), index operation, or dereference (*p). [src3]

func processUser(u *User) {
    fmt.Println(u.Name)  // u is nil here
}

Verify: Add fmt.Printf("u = %v\n", u) before the line — if it prints <nil>, you found it.

3. Trace back to where the pointer should have been assigned

Walk the call chain in the stack trace upward. Find where the nil pointer was created or returned. [src5]

func main() {
    user := findUser(999)  // Returns nil when not found
    processUser(user)       // Passes nil to processUser
}

func findUser(id int) *User {
    if id > 100 {
        return nil  // ROOT CAUSE
    }
    return &User{Name: "Alice"}
}

4. Apply the appropriate fix pattern

Choose based on the cause identified. [src3, src5]

// FIX A: Add nil check at call site
user := findUser(999)
if user == nil {
    log.Println("user not found")
    return
}
processUser(user)

// FIX B: Return error instead of nil (preferred)
func findUser(id int) (*User, error) {
    if id > 100 {
        return nil, fmt.Errorf("user %d not found", id)
    }
    return &User{Name: "Alice"}, nil
}

Verify: go run main.go — no panic. go vet ./... — no warnings.

5. Run static analysis to find other nil dereference risks

Catch similar issues across the codebase. [src4]

# Built-in
go vet ./...

# Comprehensive linter
staticcheck ./...

# Uber's nil safety analyzer
nilaway ./...

# Race detector
go test -race ./...

6. Use Delve for complex nil pointer debugging

When the nil source is not obvious from code inspection. [src4]

# Install and start
go install github.com/go-delve/delve/cmd/dlv@latest
dlv debug ./cmd/myapp

# Set breakpoint at panic line
(dlv) break main.go:25
(dlv) continue
(dlv) print u         # inspect the nil pointer
(dlv) stack           # full call chain

Code Examples

Go: nil-safe method receiver pattern

// Input:  A struct with methods that may be called on nil receivers
// Output: Methods that return zero values instead of panicking

type Config struct {
    Host string
    Port int
}

func (c *Config) GetHost() string {
    if c == nil {
        return "localhost"
    }
    return c.Host
}

func (c *Config) GetPort() int {
    if c == nil {
        return 8080
    }
    return c.Port
}

func startServer(cfg *Config) {
    host := cfg.GetHost() // Safe even if cfg is nil
    port := cfg.GetPort()
    fmt.Printf("Listening on %s:%d\n", host, port)
}

Go: generic safe dereference helpers (Go 1.18+)

// Input:  Any pointer that might be nil
// Output: The value, or a safe default

func Deref[T any](p *T) T {
    if p == nil {
        var zero T
        return zero
    }
    return *p
}

func DerefOr[T any](p *T, fallback T) T {
    if p == nil {
        return fallback
    }
    return *p
}

// Usage:
var name *string
fmt.Println(Deref(name))              // "" (zero value)
fmt.Println(DerefOr(name, "unknown")) // "unknown"

Go: constructor pattern preventing nil fields

// Input:  Required dependencies for a service
// Output: Fully initialized struct or error

type UserService struct {
    db     *sql.DB
    cache  *redis.Client
    logger *slog.Logger
}

func NewUserService(db *sql.DB, cache *redis.Client, logger *slog.Logger) (*UserService, error) {
    if db == nil {
        return nil, fmt.Errorf("NewUserService: db must not be nil")
    }
    if cache == nil {
        return nil, fmt.Errorf("NewUserService: cache must not be nil")
    }
    if logger == nil {
        logger = slog.Default()
    }
    return &UserService{db: db, cache: cache, logger: logger}, nil
}

Anti-Patterns

Wrong: Ignoring error return and dereferencing pointer

// BAD — if GetUser returns nil on error, this panics [src3]
user, _ := svc.GetUser(ctx, id)
fmt.Println(user.Name) // panic if user is nil

Correct: Always check error before using pointer

// GOOD — handle error first [src3]
user, err := svc.GetUser(ctx, id)
if err != nil {
    return fmt.Errorf("get user %d: %w", id, err)
}
fmt.Println(user.Name) // safe

Wrong: Returning typed nil pointer in interface return

// BAD — caller's err != nil check will be TRUE even on success [src6]
func validate(s string) error {
    var err *ValidationError // typed nil
    if s == "" {
        err = &ValidationError{Msg: "empty"}
    }
    return err // non-nil interface with nil *ValidationError!
}
// validate("hello") returns non-nil error!

Correct: Return plain nil for interface types

// GOOD — return untyped nil explicitly [src6]
func validate(s string) error {
    if s == "" {
        return &ValidationError{Msg: "empty"}
    }
    return nil // plain nil — err == nil works correctly
}

Wrong: Writing to uninitialized map

// BAD — nil map write panics [src1]
var counts map[string]int
counts["hello"] = 1 // panic: assignment to entry in nil map

Correct: Initialize map before use

// GOOD — always use make() for maps [src1]
counts := make(map[string]int)
counts["hello"] = 1 // safe

Wrong: No nil guard on method receiver

// BAD — panics when Next is nil [src4]
func (n *Node) String() string {
    return fmt.Sprintf("%d -> %s", n.Value, n.Next.String())
}

Correct: Nil guard on receiver

// GOOD — handle nil receiver explicitly [src4]
func (n *Node) String() string {
    if n == nil {
        return "<nil>"
    }
    return fmt.Sprintf("%d -> %s", n.Value, n.Next.String())
}

Common Pitfalls

Diagnostic Commands

# Run with race detector
go run -race main.go
go test -race ./...

# Static analysis
go vet ./...
staticcheck ./...
nilaway ./...

# Delve debugger
go install github.com/go-delve/delve/cmd/dlv@latest
dlv debug ./cmd/myapp
# break main.go:25 → continue → print varName → stack

# Build with full debug info
go build -gcflags="all=-N -l" -o myapp ./cmd/myapp

# Print all goroutine stacks (Linux/macOS)
kill -SIGQUIT <pid>

# Verbose test output
go test -v -count=1 ./...

Version History & Compatibility

Feature Available Since Notes
Nil pointer panic behavior Go 1.0 Fundamental language spec — stable [src1]
go vet nil checks Go 1.1 Basic nil dereference detection [src1]
Race detector (-race) Go 1.1 Detects concurrent nil pointer races [src1]
Delve debugger Go 1.5+ Full nil pointer inspection; latest requires Go 1.21+ [src4]
Generic helpers (Deref[T]) Go 1.18 Requires generics support [src4]
staticcheck nil analysis Go 1.18+ SA5011 checks for nil pointer dereference [src4]
nilaway (Uber) Go 1.20+ Cross-function nil flow analysis [src4]
GoLand interprocedural nil analysis 2025 IDE-based nil tracking across functions [src4]

When to Use / When Not to Use

Use When Don't Use When Use Instead
Panic says "invalid memory address or nil pointer dereference" Panic says "index out of range" Bounds checking / len() guard
Stack trace points to pointer field access Error is "assignment to entry in nil map" make(map[K]V) initialization
Intermittent crash under concurrent load Deterministic crash on startup Check initialization order
Interface != nil but method panics Interface is truly nil Standard nil check suffices

Important Caveats

Related Units