← Debugging Guides

阅读中文版本

Go — Nil Pointer Dereference Panic

Trace a nil pointer panic from the stack trace, find the unchecked nil value, and fix it with explicit error handling and regression tests.

intermediatedebugginggonilpanic

Symptoms

  • panic: runtime error: invalid memory address or nil pointer dereference
  • The service exits or the HTTP handler returns 500 with a stack trace
  • The panic appears only when a dependency returns an error or a lookup misses

Possible causes

  • A pointer field or method is used before checking whether it is nil
  • A function returns nil with an error that was ignored
  • A map lookup or type assertion returns a zero value that is used as a valid object
  • An interface holds a nil concrete value and is treated as non-nil

Diagnosis steps

Read the panic stack trace from the innermost frame

go test ./... 2>&1 | grep -B5 -A10 'nil pointer'

Interpretation

The topmost application frame identifies the exact dereference. Note the file and line, not just the package.

Reproduce with the race detector and verbose tests

go test -race -run TestName -v ./...

Interpretation

The race detector does not catch all nil panics, but it reveals concurrent access that may produce nil values. Add a focused test for the failing input.

Inspect error returns and map lookups around the panic site

grep -n 'err\|, ok :=\|return nil' path/to/file.go

Interpretation

An ignored error or a missing ok check often explains why nil reached the dereference.

Root cause

Go panics when a nil pointer is dereferenced. The value became nil because an error was ignored, a lookup missed, a constructor returned nil, or an interface contains a nil concrete value while comparing non-nil.

Fix

Check errors before using results, use the two-value form for maps and type assertions, return early on nil, and initialize pointers before use. If nil is valid, guard the dereference and define the fallback behavior explicitly.

Risk note

recover() can turn a panic into a 500 and hide the root cause. Use it only at process or request boundaries with logging and metrics, never as the primary nil check.

Verify the fix

Add a regression test for the input that produced nil and run go test -race. Confirm the handler returns a defined error instead of panicking, and check logs for the original cause.

The stack trace is the map

A nil pointer panic tells you the exact dereference site. Start at the top application frame and work outward. The bug is usually an ignored error or a missing lookup check just before that line.

user, err := store.FindUser(ctx, id)
if err != nil {
    return fmt.Errorf("find user: %w", err)
}
// If FindUser can return (nil, nil), this line panics.
return user.Email, nil

Fix patterns

user, err := store.FindUser(ctx, id)
if err != nil {
    return "", fmt.Errorf("find user %s: %w", id, err)
}
if user == nil {
    return "", ErrUserNotFound
}
return user.Email, nil

For maps, use value, ok := m[key]. For interfaces, remember that an interface holding a typed nil is not equal to nil; check the concrete value or avoid returning typed nil pointers as interfaces.

Verify

  • The failing input now returns a defined error.
  • go test -race passes.
  • A regression test reproduces the original path.
  • Logs include the operation and identifier, not only “nil pointer”.

Prevention

Return errors with context, avoid constructors that return nil without an error, and review nil checks on every path where a dependency can fail. Run go vet and static analysis in CI; they catch some, but not all, nil flows.