runtime error: invalid memory address or nil pointer dereference) occurs when you access a field, method, or index through a pointer that is nil. The fix is always one of: initialize the pointer, check for nil before access, or return an error instead of nil. Read the stack trace line number to find which variable is nil, then trace back to where it should have been assigned.dlv debug main.go (Delve debugger) — set breakpoints, inspect pointer values, and step through code to find where a pointer becomes nil.== nil. This is the #1 subtle cause of nil panics that pass standard nil checks.go vet, staticcheck, and nilaway work with Go 1.18+.(*T, error) — the pointer may be nil when err != nil. [src3]make() before writing. [src1]nil from functions returning interfaces. [src6]sync.Mutex or sync.RWMutex for shared pointer variables. [src3]| # | 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] |
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]
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.
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.
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"}
}
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.
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 ./...
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
// 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)
}
// 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"
// 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
}
// BAD — if GetUser returns nil on error, this panics [src3]
user, _ := svc.GetUser(ctx, id)
fmt.Println(user.Name) // panic if user is nil
// 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
// 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!
// 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
}
// BAD — nil map write panics [src1]
var counts map[string]int
counts["hello"] = 1 // panic: assignment to entry in nil map
// GOOD — always use make() for maps [src1]
counts := make(map[string]int)
counts["hello"] = 1 // safe
// BAD — panics when Next is nil [src4]
func (n *Node) String() string {
return fmt.Sprintf("%d -> %s", n.Value, n.Next.String())
}
// 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())
}
== nil. Always return plain nil from functions returning interfaces, not a typed nil variable. [src6](*T, error) can return nil for the pointer when err is non-nil. Always check error before using the pointer. [src3]len(), range, and append(). A nil map panics on write. Always make() maps before writing. [src1]MyStruct{} initializes value fields to zero but pointer fields to nil. Use constructor functions. [src7]sync.Mutex; run go test -race. [src3]x.(ConcreteType) panics if x is nil. Use the comma-ok form: v, ok := x.(ConcreteType). [src1]# 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 ./...
| 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] |
| 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 |
defer/recover can catch nil pointer panics, it masks the bug. The program state is likely corrupt. Only use recover at top-level request handlers as a safety net. [src1]go test -race. Non-concurrent nil panics always reproduce with the same inputs. [src3](*int)(nil) and (*string)(nil) assigned to interface{} produce different non-nil values. [src6]new() returns a pointer to zeroed memory, not nil: p := new(MyStruct) gives &MyStruct{}, not nil. But pointer fields within that struct are still nil. [src2]