Skip to content
gohacks.dev
Basics

Invalid Memory Address or Nil Pointer Dereference in Go

Fix the Go panic invalid memory address or nil pointer dereference: the common causes, how to read the trace, nil-safe methods and tests that prevent it.

7 min read
Summarize in AI
Flat illustration of a grid of heap cells with the garbage collector sweeping across it.

This is the panic that Go programmers meet most often, and the one that has the most reasons to occur. The message is always the same: panic: runtime error: invalid memory address or nil pointer dereference, followed by a line starting [signal SIGSEGV: segmentation violation ...] and a goroutine trace. It means exactly one thing: the program followed a pointer that was nil. Everything else, which pointer, why it was nil, is in the code, and the trace points at the place.

Tested with Go 1.25.6, macOS on Apple Silicon (arm64)

This page shows how to read the trace, then the causes in order of frequency, each with a reproduction. It belongs to the error handling guide and has relatives in index out of range and the nil map write. Everything ran on Go 1.25.6.

Reading the trace

A trace has this shape (the paths and addresses here are illustrative), and the parts that matter are few:

Text
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x8 pc=0x...]

goroutine 1 [running]:
main.(*Server).Handle(...)
	/app/server.go:42
main.main()
	/app/main.go:17 +0x1c
exit status 2

Read it from the top. The first line says what happened. The addr=0x8 in the signal line is the address the program tried to read, and a small number (0x0, 0x8, 0x10) means a field at that offset of a nil struct pointer, which confirms the cause. Then find the first frame in your own code, here server.go:42, and open that line. The expression there contains a pointer that was nil: a field access s.conn.Close() where s.conn is nil, or a method that uses its receiver. The frames below it are the callers and show the path that led there. Frames from runtime or the standard library sit above yours if the panic happened inside them, and then the first of your frames is the call you made with the bad value.

You can reproduce the message and recover it, to see it without a crash:

main.goGo
package main

import "fmt"

type Server struct {
	Name string
	Conn *Conn
}

type Conn struct{ ID int }

func main() {
	defer func() { fmt.Println("recovered:", recover()) }()
	s := &Server{Name: "api"} // Conn was never set
	fmt.Println(s.Name)
	fmt.Println(s.Conn.ID)
}
Text
api
recovered: runtime error: invalid memory address or nil pointer dereference

s is fine and s.Conn is nil, so reading .ID through it panics. The first Println works, which is a typical sign that the failing expression is one step deeper than the object you are looking at.

Cause 1: using a result after ignoring the error

By far the most common source is a function that returns (*T, error), called with the error dropped. On failure it returns nil and an error, and the code uses the nil:

main.goGo
package main

import (
	"fmt"
	"os"
)

func main() {
	defer func() { fmt.Println("recovered:", recover()) }()
	f, _ := os.Open("/does/not/exist")
	fmt.Println(f.Name())
	defer f.Close()
	buf := make([]byte, 4)
	f.Read(buf)
}
Text
recovered: runtime error: invalid memory address or nil pointer dereference

os.Open returned a nil *os.File and an error that was thrown away with _. Interestingly, f.Name() on a nil *os.File panics with this message, because the method reads a field of the receiver. The fix is the same one it has been since the first page: check the error before using the value, and return when it is not nil (error handling in Go). The unchecked-error linter finds these before they run (errcheck).

Cause 2: a struct field that nobody set

A struct with a pointer, map, slice-of-pointers or interface field starts with the zero value, which is nil. Code that assumes the field is initialised panics at the first use:

main.goGo
package main

import "fmt"

type Cache struct{ hits int }

func (c *Cache) Hit() { c.hits++ }

type Service struct {
	cache *Cache
}

func NewService() *Service {
	return &Service{} // forgot cache
}

func (s *Service) Get() string {
	s.cache.Hit()
	return "value"
}

func main() {
	defer func() { fmt.Println("recovered:", recover()) }()
	fmt.Println(NewService().Get())
}
Text
recovered: runtime error: invalid memory address or nil pointer dereference

The method Hit has a pointer receiver and was called on a nil *Cache. Calling a method on a nil pointer is legal, and the panic happens on c.hits++, the first access to a field. The fix is to initialise every dependency in the constructor, and to make construction the only way to get a Service (unexported fields and a New function). If a dependency is optional, give it a no-op default instead of nil (constructors and functional options show how).

Cause 3: a nil interface or function value

Calling a method on a nil interface panics with the same message, because there is no concrete value to dispatch to. So does calling a nil function variable:

main.goGo
package main

import "fmt"

type Notifier interface{ Notify(string) }

type Handler struct {
	Notifier Notifier
	OnDone   func()
}

func try(name string, f func()) {
	defer func() { fmt.Printf("%-14s %v\n", name, recover()) }()
	f()
}

func main() {
	h := Handler{}
	try("nil interface", func() { h.Notifier.Notify("hi") })
	try("nil func", func() { h.OnDone() })
}
Text
nil interface  runtime error: invalid memory address or nil pointer dereference
nil func       runtime error: invalid memory address or nil pointer dereference

Both panic with invalid memory address or nil pointer dereference. Guard optional callbacks with an if h.OnDone != nil check, or fill them with a no-op at construction. For interfaces there is a second trap: an interface holding a typed nil pointer is not nil, so if n != nil passes and the method then panics inside, or worse, runs with a nil receiver. The nil interface that is not nil explains why.

Cause 4: nil from a map lookup or a type assertion

Map values of pointer type come back nil when the key is absent, and code that goes straight to a field panics:

main.goGo
package main

import "fmt"

type User struct{ Name string }

func main() {
	defer func() { fmt.Println("recovered:", recover()) }()
	users := map[string]*User{"ada": {Name: "Ada"}}
	fmt.Println(users["ada"].Name)
	fmt.Println(users["linus"].Name)
}
Text
Ada
recovered: runtime error: invalid memory address or nil pointer dereference

Use the two-value form, u, ok := users["linus"], and check ok (checking for a key). The same applies to a lookup in a slice of pointers and to the result of x.(*T) when written without the ok form.

Cause 5: a pointer that was valid and no longer is

Race conditions and lifecycle mistakes produce nil pointers that were not nil a moment ago: a field cleared by Close while another goroutine reads it, a struct reused after reset, a callback that fires after teardown. These show up intermittently. The race detector finds the unsynchronised ones (the race detector), and the cure is to make ownership explicit, with a mutex around the field or a channel that serialises access.

Making methods safe on nil

Sometimes a nil receiver is a legitimate state, and the type can say so. A common idiom in the standard library is a method that treats nil as empty:

main.goGo
package main

import "fmt"

type Tree struct {
	Left, Right *Tree
	Value       int
}

// Sum of a nil tree is zero, so the recursion needs no nil checks of its own.
func (t *Tree) Sum() int {
	if t == nil {
		return 0
	}
	return t.Value + t.Left.Sum() + t.Right.Sum()
}

func main() {
	t := &Tree{Value: 1, Left: &Tree{Value: 2}, Right: &Tree{Value: 3, Left: &Tree{Value: 4}}}
	var empty *Tree
	fmt.Println(t.Sum(), empty.Sum())
}
Text
10 0

A nil *Tree is an empty tree, and every method that handles it makes callers simpler. Use this where "nothing" is a natural value of the type, as with trees, lists and builders, and not to hide missing dependencies: a nil Service that silently returns defaults is a bug that stopped panicking.

Preventing it

  1. Check errors, always. Most nil pointers are an unchecked error in disguise.
  2. Initialise in constructors, and keep the struct's fields unexported so nobody builds a half-set value.
  3. Return values, not pointers, when nil is not meaningful: a time.Duration or a small struct cannot be nil.
  4. Use the comma-ok forms for maps and assertions.
  5. Write the test with the nil. A table row with a nil dependency or a failing constructor reaches the branch that production reaches at 3 a.m. (testing error paths).
  6. Run with -race in CI to catch the pointers that vanish under concurrency.

For the exact semantics of nil in Go, the specification's section on the zero value and its rules for selectors say when a nil pointer dereference panics. The official runtime error text is documented in the runtime package.

Also inTooling
Taggederror fix
Share

Keep reading

Get new articles by email

We send one email when a new article is published. No spam.

Unsubscribe in one click. No spam, ever.