Go Panic and Recover
Panic is Go's mechanism for handling unrecoverable errors — situations where the program cannot continue safely. Recover allows a program to catch a panic before it crashes the entire application. Together they form Go's alternative to traditional exception handling.
What Is Panic?
A panic stops the normal execution of the current function immediately. Go then unwinds the call stack, running any deferred functions along the way, and finally crashes the program with an error message.
package main
import "fmt"
func main() {
fmt.Println("Start")
panic("something went terribly wrong")
fmt.Println("This line never runs")
}
Output:
Start
goroutine 1 [running]:
main.main()
/tmp/main.go:6 +0x68
panic: something went terribly wrong
Panic Flow Diagram
Normal execution → panic() is called
│
▼
Stop current function immediately
│
▼
Run all deferred functions (in LIFO order)
│
▼
Propagate up the call stack
│
▼
Program crashes with panic message
(unless recovered)
When Does Go Panic Automatically?
Go panics on its own when the program performs illegal operations.
| Situation | Example |
|---|---|
| Accessing an out-of-range slice index | s[10] on a slice of length 3 |
| Dereferencing a nil pointer | (*p).Name when p is nil |
| Dividing by zero | x / 0 with integer types |
| Closing a closed channel | Closing the same channel twice |
What Is Recover?
Recover stops a panic from crashing the program. It only works inside a deferred function. When called inside a deferred function during a panic, it catches the panic value and allows execution to continue normally after the panicking function returns.
package main
import "fmt"
func safeDivide(a, b int) {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
}
}()
result := a / b
fmt.Println("Result:", result)
}
func main() {
safeDivide(10, 2) // works fine
safeDivide(10, 0) // panics, but recover catches it
fmt.Println("Program continues after recovery")
}
Output:
Result: 5
Recovered from panic: runtime error: integer divide by zero
Program continues after recovery
Recover Must Be in a Deferred Function
// WRONG – recover here does nothing
func bad() {
r := recover() // not deferred, useless outside a defer
fmt.Println(r)
}
// CORRECT – recover works only inside defer
func good() {
defer func() {
if r := recover(); r != nil {
fmt.Println("caught:", r)
}
}()
panic("test")
}
Panic with a Custom Message
package main
import "fmt"
func validateAge(age int) {
if age < 0 {
panic(fmt.Sprintf("invalid age: %d — age cannot be negative", age))
}
fmt.Println("Valid age:", age)
}
func main() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Error caught:", r)
}
}()
validateAge(25) // Valid age: 25
validateAge(-5) // triggers panic, caught by recover
}
Output:
Valid age: 25
Error caught: invalid age: -5 — age cannot be negative
Panic vs Error Handling
| Situation | Use |
|---|---|
| Expected failures (file not found, bad input) | Return an error value |
| Programming bugs (nil pointer, out-of-range) | Let panic occur |
| Server must not crash on any request | Use recover in middleware |
Key Points
- Panic immediately stops the current function and begins unwinding the call stack
- Deferred functions always run during a panic before the program crashes
- Recover catches a panic and allows the program to continue — only works inside a deferred function
- Use regular error returns for expected failures; reserve panic for genuine programming bugs
- Web servers commonly use recover in middleware to prevent one bad request from crashing the whole server
