Defer, Panic, and Recover
defer
A defer statement schedules a function call to run at the moment the surrounding function returns โ whether it returns normally, returns early, or panics. The deferred call is not executed immediately; it is pushed onto a stack and runs when the function exits.
Argument Evaluation Is Immediate
A deferred call's arguments are evaluated right away, even though the call itself is deferred. Only the execution is postponed:
LIFO Order for Multiple Defers
When multiple defer statements appear in the same function, they execute in last in, first out (LIFO) order โ like a stack. The last defer to be registered runs first:
Classic defer Use Cases
Resource cleanup. defer guarantees a resource is closed even if an early return or error occurs:
Mutex unlock. Pairing Lock with a deferred Unlock prevents deadlocks from forgotten unlocks:
WaitGroup done. Ensures the counter is decremented even if the goroutine returns early:
panic
panic immediately stops the current function, runs any deferred functions in that function, then unwinds the call stack โ running deferred functions at each frame โ until the entire goroutine terminates. If no recover is in place, the program crashes with a stack trace.
Panics carry a value โ typically a string or an error. Deferred functions still run even during a panic, so cleanup code protected by defer is always executed.
recover
recover() stops a panic and returns the panic value. It has one strict rule: it must be called inside a deferred function โ calling it anywhere else is a no-op.
The idiom defer func() { if r := recover(); r != nil { ... } }() is the standard recover pattern. Note the immediately-invoked function literal โ the () at the end is required.
What recover Returns
recover() returns nil when there is no active panic. The pattern if r := recover(); r != nil therefore distinguishes a real panic from a normal return.
When NOT to Panic
The most important rule: use error returns for anything that could realistically fail at runtime. Panic is appropriate only in a narrow set of situations:
| Appropriate | Inappropriate |
|---|---|
| Invariant violated that proves a bug | File not found |
Must-style init functions (e.g. regexp.MustCompile) | Network timeout |
| Nil receiver where nil is never valid | Invalid user input |
| Index into a slice where the index is programmer-controlled | Database query failure |
When in doubt, return an error. Panics that escape package boundaries are surprising to callers and make APIs hard to reason about.
Knowledge Check
In what order do multiple deferred functions run?
Where must recover() be called to catch a panic?
When is a defer statement's argument evaluated?