Skip to content

Fix: Go panic: runtime error: invalid memory address or nil pointer dereference

FixDevs ·

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 2

Or 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 dereference

The 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 pointervar p *MyStruct declares a nil pointer. Accessing p.Field panics.
  • 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 writevar m map[string]int; m["key"] = 1 panics. Maps must be initialized with make.
  • Nil receiver — calling a method on a nil pointer receiver panics unless the method explicitly handles nil.
  • Chained nil accessuser.Address.City panics if user.Address is nil, even if user is 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 nil

The 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 _, _ or val, _ 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 map

Fix — 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{}  // Safe

Fix 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 accessrecover 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 +0x28

Go 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 inspection

Still 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.go

The 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.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles