Skip to content
gohacks.dev
Basics

Every Go Runtime Panic and What Causes Each One of Them

A reference to Go runtime panics with the exact message each one prints, what causes it and how to fix it, plus the fatal errors that cannot be recovered.

6 min read
Summarize in AI
Flat illustration of an editor window full of code.

When a Go program dies with panic: runtime error: ..., the part after runtime error: identifies exactly what the runtime refused to do. The set of messages is small and stable, and knowing it by heart turns most crashes into a ten-second diagnosis. This page is the reference: each panic, the message as printed by Go 1.25.6, what causes it, and where to read more. Next to it sits the second family, fatal errors, which look similar but cannot be caught.

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

It belongs to the error handling guide. The deeper pages for the most common ones are linked in the table.

The panics, reproduced

Every message below was produced by running the code in this block, which catches each panic and prints what recover() returned:

main.goGo
package main

import (
	"fmt"
	"sync"
	"time"
)

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

type T struct{ x int }

func main() {
	var (
		p      *T
		m      map[string]int
		s          = []int{1, 2, 3}
		i          = 7
		zero       = 0
		neg        = -1
		a      any = "text"
		fn     func()
		ch     chan int
		closed = make(chan int)
		keyed  = map[any]int{}
	)
	close(closed)

	try("nil pointer", func() { _ = p.x })
	try("index", func() { _ = s[i] })
	try("slice bounds", func() { _ = s[1:i] })
	try("divide by zero", func() { _ = 10 / zero })
	try("nil map write", func() { m["k"] = 1 })
	try("type assertion", func() { _ = a.(int) })
	try("nil func call", func() { fn() })
	try("close nil chan", func() { close(ch) })
	try("close closed chan", func() { close(closed) })
	try("send on closed", func() { closed <- 1 })
	try("make negative len", func() { _ = make([]int, neg) })
	try("make negative cap", func() { _ = make([]int, 1, neg) })
	try("unhashable key", func() { keyed[[]int{1}] = 1 })
	try("uncomparable ==", func() {
		var x, y any = []int{1}, []int{1}
		_ = x == y
	})
	try("slice to array", func() { _ = [4]int(s) })
	try("negative shift", func() { _ = 1 << neg })
	try("negative WaitGroup", func() {
		var wg sync.WaitGroup
		wg.Add(-1)
	})
	try("negative ticker", func() { time.NewTicker(0) })
}
Text
nil pointer        runtime error: invalid memory address or nil pointer dereference
index              runtime error: index out of range [7] with length 3
slice bounds       runtime error: slice bounds out of range [:7] with capacity 3
divide by zero     runtime error: integer divide by zero
nil map write      assignment to entry in nil map
type assertion     interface conversion: interface {} is string, not int
nil func call      runtime error: invalid memory address or nil pointer dereference
close nil chan     close of nil channel
close closed chan  close of closed channel
send on closed     send on closed channel
make negative len  runtime error: makeslice: len out of range
make negative cap  runtime error: makeslice: cap out of range
unhashable key     runtime error: hash of unhashable type []int
uncomparable ==    runtime error: comparing uncomparable type []int
slice to array     runtime error: cannot convert slice with length 3 to array or pointer to array with length 4
negative shift     runtime error: negative shift amount
negative WaitGroup sync: negative WaitGroup counter
negative ticker    non-positive interval for NewTicker

Reference table

Panic messageCauseFix and further reading
invalid memory address or nil pointer dereferenceA field read, a method that uses its receiver, or a call through a nil pointer, interface or functionCheck errors and initialise fields: nil pointer dereference
index out of range [7] with length 3An index at or beyond the length, or negativeGuard with len, range instead: index out of range
slice bounds out of range [:7] with capacity 3A slice expression with a bound outside 0 <= low <= high <= capClamp bounds, use strings.Cut: slice bounds
integer divide by zeroInteger / or % with a zero divisorCheck the divisor first
assignment to entry in nil mapWriting to a map that was never mademake it first: nil map write
interface conversion: interface {} is string, not intA single-value type assertion that failedUse comma-ok: interface conversion
close of nil channelclose on a channel variable that is nilCreate the channel before closing it
close of closed channelclose called twiceClose from the single sender, once: closing channels
send on closed channelA send after closeLet the receiver stop sending, not the other way: send on closed channel
makeslice: len out of rangemake with a negative or absurdly large lengthValidate sizes that come from input
makeslice: cap out of rangemake with a bad capacityThe same
runtime error: hash of unhashable type []intA slice, map or function used as a key in a map[any]Use a comparable key such as a string
comparing uncomparable type []int== on interface values holding slices, maps or functionsCompare with slices.Equal or reflect.DeepEqual
cannot convert slice with length 3 to array or pointer to array with length 4Converting a slice to an array type longer than the sliceCheck len first
negative shift amountA shift by a negative signed countValidate the count
sync: negative WaitGroup counterDone called more often than AddPair them carefully: WaitGroup
non-positive interval for NewTickertime.NewTicker or NewTimer-style call with zero or negative durationValidate the duration: tickers

Two rows deserve a note. Integer division by zero panics, but floating point does not: 1.0 / 0.0 computed at run time gives +Inf, and 0.0 / 0.0 gives NaN, so a float calculation can go wrong without a crash and show up later as a NaN in your output (floating point precision). And the panic calls made by library code, strings.Repeat with a negative count, regexp.MustCompile, follow the same shape but are raised by the library, not the runtime (panic or error).

Fatal errors, which are not panics

A short list of failures prints fatal error: instead of panic:. They end the process immediately, run no deferred functions and cannot be stopped by recover:

MessageCause
fatal error: all goroutines are asleep - deadlock!Every goroutine is blocked and nothing can wake them: the deadlock error
fatal error: concurrent map writes (or concurrent map read and map write)Unsynchronised map access from several goroutines: concurrent map writes
fatal error: stack overflow (printed with runtime: goroutine stack exceeds 1000000000-byte limit)Unbounded recursion
fatal error: out of memoryThe allocator could not get memory
fatal error: sync: unlock of unlocked mutexUnlock on a mutex that is not locked
fatal error: unexpected signal during runtime executionA memory fault in cgo or unsafe code

The mutex one is a good example of why the split exists. Unlocking an unlocked mutex means the program's synchronisation is corrupt, and continuing would be worse than stopping, so the runtime refuses to let a recover hide it (mutexes). The deadlock message is the reverse: the runtime detected that the program can never make progress, and there is nothing a handler could do.

Reading the first line

A panic's output always starts with the same frame: panic: followed by the value. Most runtime panics print with a runtime error: prefix, as the reproduction above shows, while a few (the nil map write, channel misuse) print their text bare, and panics raised by library code such as sync and time are plain strings. The program exits with status 2 either way. For a re-raised panic you see [recovered, repanicked] (re-panicking). Below it the goroutine trace lists the frames from the panic site outward, and the first frame in your own package is where to look. A [signal SIGSEGV ...] line after a nil dereference includes the faulting address, which is small (like 0x8) when a field of a nil struct was read.

Catching them in code

Runtime panics are values of types that implement runtime.Error, which is how a recover boundary tells them from deliberate panics:

Go
if r := recover(); r != nil {
	if re, ok := r.(runtime.Error); ok {
		log.Printf("runtime panic (bug): %v", re)
	}
}

Use that only at a boundary that must survive, such as a request handler (recover). Everywhere else, the fix for a runtime panic is to remove the bug that raised it, using the table above to find the matching page. The runtime's own documentation of the error type is in the runtime package, and the language rules that produce several of these panics are in the specification.

Also inTooling
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.