Introduction
Go provides defer, panic and recover as a separate mechanism from ordinary error returns, intended for truly exceptional or unrecoverable situations rather than everyday error handling. defer schedules a function call to run when the surrounding function returns, in last-in-first-out (LIFO) order. panic stops the normal execution of a function and begins unwinding the call stack, running any deferred calls along the way. recover, when called inside a deferred function, regains control of a panicking goroutine and stops the unwinding. Idiomatic Go reserves panic/recover for programmer errors or unrecoverable conditions, not for expected failures, which should use the error return pattern instead.
Cricket analogy: Like a team physio (defer) always being called in at the end of play regardless of outcome, a serious injury (panic) halts the match and unwinds through the innings until team management (recover) steps in, reserved for genuine emergencies not routine dismissals.
Syntax
func example() {
defer fmt.Println("deferred: runs last, when example() returns")
defer func() {
if r := recover(); r != nil {
fmt.Println("recovered from panic:", r)
}
}()
panic("something went badly wrong")
}
Explanation
defer statements are pushed onto a stack tied to the enclosing function; when that function returns (normally or via panic), deferred calls run in LIFO order, making defer ideal for cleanup like closing files or unlocking mutexes. panic(value) immediately stops normal execution, runs deferred calls in the current goroutine, and propagates the panic up the call stack until a deferred function calls recover() or the program crashes with a stack trace. recover() only has an effect when called directly inside a deferred function; it stops the panic from propagating further and returns the value passed to panic. Because panic/recover unwinds the stack and skips normal control flow, Go style reserves it for truly exceptional situations, such as programmer bugs (e.g. index out of range) or unrecoverable startup failures, not for expected, recoverable conditions like a missing file, which should be returned as an error instead.
Cricket analogy: Like a chain of fielders' end-of-over duties (defer) executing in reverse order they were assigned, a boundary rope incident (panic) runs all pending duties and keeps escalating until the third umpire (recover), called directly from the review booth, stops it, reserved for a genuine crisis not a routine dropped catch.
Example
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)
}
}()
result = a / b // panics on division by zero
return result, nil
}
func main() {
result, err := safeDivide(10, 0)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Result:", result)
}
result2, err2 := safeDivide(10, 2)
fmt.Println(result2, err2)
}
Output
Error: recovered: runtime error: integer divide by zero
5 <nil>
Do not use panic/recover as a substitute for normal error returns. Reserve panic for truly unrecoverable situations (like corrupted internal state or programmer bugs), and use error return values for expected, recoverable failure conditions such as invalid input or missing files.
Key Takeaways
- defer schedules a call to run when the surrounding function returns, in LIFO order.
- panic stops normal execution and begins unwinding the stack, running deferred calls.
- recover only works when called directly inside a deferred function.
- recover stops the panic from propagating and returns the panic's value.
- panic/recover is for exceptional, unrecoverable situations, not routine error handling.
- Idiomatic Go still prefers returning an error over panicking for expected failure cases.
Practice what you learned
1. In what order do multiple defer statements in the same function execute?
2. What does calling panic() do?
3. Where must recover() be called for it to have any effect?
4. According to idiomatic Go style, when should panic be used?
5. What does Go have for exception-style control flow, comparable to try/catch in other languages?
Was this page helpful?
You May Also Like
Error Handling in Go
Learn Go's idiomatic error handling using the built-in error interface and explicit error return values instead of exceptions.
Custom Errors and errors.Is/As in Go
Build custom error types in Go and use errors.Is and errors.As to inspect and match wrapped errors.
Functions in Go
Learn how to declare, call, and use functions as first-class values in Go.
Goroutines in Go
Goroutines are lightweight, runtime-managed threads that let Go programs run functions concurrently with minimal overhead.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics