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.

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:
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) })
}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 NewTickerReference table
| Panic message | Cause | Fix and further reading |
|---|---|---|
invalid memory address or nil pointer dereference | A field read, a method that uses its receiver, or a call through a nil pointer, interface or function | Check errors and initialise fields: nil pointer dereference |
index out of range [7] with length 3 | An index at or beyond the length, or negative | Guard with len, range instead: index out of range |
slice bounds out of range [:7] with capacity 3 | A slice expression with a bound outside 0 <= low <= high <= cap | Clamp bounds, use strings.Cut: slice bounds |
integer divide by zero | Integer / or % with a zero divisor | Check the divisor first |
assignment to entry in nil map | Writing to a map that was never made | make it first: nil map write |
interface conversion: interface {} is string, not int | A single-value type assertion that failed | Use comma-ok: interface conversion |
close of nil channel | close on a channel variable that is nil | Create the channel before closing it |
close of closed channel | close called twice | Close from the single sender, once: closing channels |
send on closed channel | A send after close | Let the receiver stop sending, not the other way: send on closed channel |
makeslice: len out of range | make with a negative or absurdly large length | Validate sizes that come from input |
makeslice: cap out of range | make with a bad capacity | The same |
runtime error: hash of unhashable type []int | A 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 functions | Compare with slices.Equal or reflect.DeepEqual |
cannot convert slice with length 3 to array or pointer to array with length 4 | Converting a slice to an array type longer than the slice | Check len first |
negative shift amount | A shift by a negative signed count | Validate the count |
sync: negative WaitGroup counter | Done called more often than Add | Pair them carefully: WaitGroup |
non-positive interval for NewTicker | time.NewTicker or NewTimer-style call with zero or negative duration | Validate 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:
| Message | Cause |
|---|---|
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 memory | The allocator could not get memory |
fatal error: sync: unlock of unlocked mutex | Unlock on a mutex that is not locked |
fatal error: unexpected signal during runtime execution | A 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:
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.


