The Error
Your Go program crashes mid-run, dumps a stack trace, and exits with status 2:
goroutine 1 [running]:
main.main()
/home/user/app/main.go:15 +0x1c
exit status 2
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x47d8a6]
The culprit: your code tried to read or write through a pointer sitting at address 0x0 โ Go's way of saying "this was never assigned anything real." That address is always invalid, so the OS kills the process on the spot.
Root Cause
Go doesn't auto-initialize pointer types, interfaces, maps, slices, channels, or function values. Their zero value is nil. Touch any of them before assigning a real value, and you'll get this panic.
Five scenarios that cause it constantly in real codebases:
- Calling a method on a nil struct pointer (
var u *User; u.Name()) - Discarding the error from a function that can return
nilon failure - Accessing a map or slice declared with
varbut never initialized - Storing an interface that wraps a nil concrete pointer
- Using a failed type assertion result without checking
ok
Fix 1 โ Check for nil before use
Guard every pointer or interface before you touch it. One extra if is all it takes.
// Bad โ panics if resp is nil
func processResponse(resp *http.Response) string {
return resp.Status
}
// Good โ explicit nil check
func processResponse(resp *http.Response) string {
if resp == nil {
return "no response"
}
return resp.Status
}
Fix 2 โ Always check errors from functions that return pointers
This is the most common source of nil panics in Go. Functions like os.Open, database queries, and JSON unmarshal all return (value, error). When the call fails, value is nil โ and if you silence the error with _, you're one line away from a crash.
// Bad โ ignores the error, then panics on nil
f, _ := os.Open("config.json")
data, _ := io.ReadAll(f) // f is nil if Open failed
// Good โ check the error first
f, err := os.Open("config.json")
if err != nil {
log.Fatal(err)
}
defer f.Close()
data, err := io.ReadAll(f)
if err != nil {
log.Fatal(err)
}
Fix 3 โ Initialize structs and maps before use
Declaring a struct with var leaves all its pointer fields as nil. You have to explicitly initialize nested structs before writing to them.
// Bad โ Config.DB is never initialized
type Config struct {
DB *DatabaseConfig
}
var cfg Config
cfg.DB.Host = "localhost" // panic: DB is nil
// Good โ initialize the nested struct
cfg := Config{
DB: &DatabaseConfig{},
}
cfg.DB.Host = "localhost"
// Or with new()
cfg.DB = new(DatabaseConfig)
cfg.DB.Host = "localhost"
Maps hit the same wall:
// Bad
var cache map[string]int
cache["key"] = 1 // panic: assignment to entry in nil map
// Good
cache := make(map[string]int)
cache["key"] = 1
Fix 4 โ Use the comma-ok pattern for type assertions
Skip the ok check on a type assertion and it panics the moment the type doesn't match. Two extra characters save you from the crash.
// Bad
var i interface{} = "hello"
n := i.(int) // panic: interface conversion
// Good
n, ok := i.(int)
if !ok {
fmt.Println("not an int")
return
}
Fix 5 โ Reading the stack trace to find the exact line
Don't guess โ the panic output tells you precisely where it happened. Set GOTRACEBACK=all before you start digging:
GOTRACEBACK=all go run main.go
Scan the trace for the first frame inside your own package, not Go runtime internals. That's your nil dereference. Here's a concrete example:
panic: runtime error: invalid memory address or nil pointer dereference
goroutine 1 [running]:
main.processUser(...)
/home/user/app/main.go:23 โ your code, line 23
main.main()
/home/user/app/main.go:10 +0x68
Jump to line 23 in main.go and look for a pointer or interface used without a nil guard.
Fix 6 โ Use recover only as a last resort
Building a server or middleware that must survive unexpected panics? Wrap risky calls with defer + recover. Just be clear about what this is: a crash net, not a fix. You still need to root-cause the nil and plug it properly.
func safeCall(fn func()) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recovered from panic: %v", r)
}
}()
fn()
return nil
}
Verification
Run the program again and confirm the panic is gone:
go run main.go
Better yet, pin the fix with a unit test so it can't quietly regress:
func TestProcessResponseNil(t *testing.T) {
result := processResponse(nil)
if result != "no response" {
t.Errorf("expected 'no response', got %q", result)
}
}
Run go test ./... โ green output with no panics means the fix holds.
Prevention
- Run with the race detector during development:
go run -race main.goโ catches concurrent nil writes that are nearly impossible to reproduce otherwise. go vet ./...before every commit; it flags some nil dereference patterns statically at zero cost.- staticcheck and
golangci-lintcatch nil pointer risks thatgo vetmisses โ worth wiring into CI. - Return concrete errors instead of nil pointers when a function fails. It makes nil impossible to silently ignore.
- For heap-only structs, write a constructor:
func NewUser() *User { return &User{scores: make([]int, 0)} }โ callers always get an initialized value.

