panic and recover
A panic stops normal execution: the current function quits, deferred calls run as the panic travels up the call stack, and if nothing stops it, the program crashes with a message and a stack trace. Panics are for bugs and impossible situations, not for ordinary errors. This lesson covers what causes panics, how they unwind, recover, and the narrow cases where using them is right.
What causes a panic
The runtime panics on programming mistakes it can't let continue:
| Cause | Example |
|---|---|
| Index out of range | s[10] on a 3-element slice |
| Nil pointer dereference | p.Name when p is nil |
| Writing to a nil map | var m map[string]int; m["a"] = 1 |
| Failed type assertion | v.(int) when v holds a string |
| Integer division by zero | a / b with b == 0 |
| Closing a closed channel | Lesson 52 |
Your own code can panic too, by calling the built-in panic with any value — usually an error or a string.
How a panic unwinds
When a function panics, it stops immediately. Its deferred calls run, then its caller stops and runs its deferred calls, and so on up to the top of the goroutine, where the program prints the panic value and a stack trace and exits with status 2:
package main import "fmt" func level3() { defer fmt.Println("level3 deferred") panic("something impossible happened") } func level2() { defer fmt.Println("level2 deferred") level3() fmt.Println("never printed") } func main() { defer fmt.Println("main deferred") level2() }
level3 deferred level2 deferred main deferred panic: something impossible happened goroutine 1 [running]: … exit status 2
The full stack trace (cut short here) lists every function on the way, with file names and line numbers — usually exactly what you need to find the bug.
recover
recover stops a panic. It only works inside a deferred function: it returns the value passed to panic, and the function whose defer called it then returns normally to its caller. Outside a panic, recover returns nil:
package main import "fmt" func safeDivide(a, b int) (result int, err error) { defer func() { if r := recover(); r != nil { err = fmt.Errorf("recovered: %v", r) } }() return a / b, nil } func main() { fmt.Println(safeDivide(10, 2)) fmt.Println(safeDivide(1, 0)) fmt.Println("program continues") }
5 <nil> 0 recovered: runtime error: integer divide by zero program continues
Setting the named result err in the deferred function turns the panic into an ordinary error for the caller.
Where recover belongs
Recovering should be rare. The legitimate places are boundaries where one failing task mustn't bring down everything else — for instance, a server handling many independent requests. Go's HTTP server does this for you: a panic in one handler is logged and that connection is closed, while the server keeps running. A job runner might do the same for each job:
package main import ( "fmt" "strings" ) func runJob(name string, job func()) { defer func() { if r := recover(); r != nil { fmt.Printf("job %q crashed: %v\n", name, r) } }() job() fmt.Printf("job %q done\n", name) } func main() { runJob("resize", func() {}) runJob("parse", func() { var fields []string _ = strings.ToUpper(fields[2]) // a bug }) runJob("email", func() {}) }
job "resize" done job "parse" crashed: runtime error: index out of range [2] with length 0 job "email" done
Recovering hides bugs if you're not careful: always log what happened. A panic in a goroutine can only be recovered by a deferred call in that same goroutine; otherwise it crashes the whole program (lesson 51).
When to panic yourself
Return errors for anything that can reasonably go wrong: bad input, missing files, network failures. Panic only for:
- Programmer errors — a broken invariant that means the code itself is wrong, like an impossible
switchcase. - Setup that can't fail at run time, by convention in functions named
Must…:regexp.MustCompileandtemplate.Mustpanic on a bad pattern written into your source code, where an error return would just be noise.
package main import ( "fmt" "strconv" ) // MustAtoi is for values written into the program, never for user input. func MustAtoi(s string) int { n, err := strconv.Atoi(s) if err != nil { panic(fmt.Sprintf("MustAtoi(%q): %v", s, err)) } return n } var defaultLimit = MustAtoi("100") func main() { fmt.Println(defaultLimit) }
100
Never let a panic cross a package's public API as a way of reporting ordinary failures: callers expect errors.
Errors versus panics
| Error | Panic | |
|---|---|---|
| For | Expected failures | Bugs, impossible states |
| Visible in the signature | Yes, as a result | No |
| Caller must | Check it | Nothing — it propagates |
| Typical response | Handle, wrap, return | Crash and fix the code |
Try it yourself
Run the program, then remove the deferred recover and see the difference.
processed a <nil> processed b <nil> item 2: runtime error: index out of range [2] with length 2