defer, panic & recover
These three keywords control what happens at the edges of a function — cleanup, crashes, and crash recovery. By the end you'll use defer for guaranteed cleanup, know when panic is appropriate, and use recover to keep a program alive after an unexpected failure.
Learn defer, panic & recover in our free Go course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick recall.
Part of the free Go course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
What You'll Learn in This Lesson
📊 The Three Keywords
Keyword
What it does
When to use
defer
Run a call when the function returns
Cleanup: close files, unlock mutexes
panic
Abort, run defers, unwind the stack
Truly unrecoverable bugs only
recover
Stop a panic (only inside a defer)
Keep a server alive after one bad request
Rule of thumb: for expected problems (bad input, missing file) return an error . Save panic for impossible-state bugs. We'll cover idiomatic errors more in the next lesson.
1️⃣ defer: Cleanup That Always Runs
defer schedules a call to run when the surrounding function returns — by any path, including an early return or a panic. Multiple defers run in last-in, first-out order, like a stack. This is why the deferred lines below print in reverse.
The everyday pattern is to put cleanup right next to the thing it cleans up, so the two can never drift apart as the function grows.
Your turn. Add the one keyword that makes "disconnecting..." print on the way out instead of immediately.
2️⃣ Argument Evaluation Timing
A subtle but important rule: a deferred call's arguments are evaluated immediately , at the defer statement, even though the call itself runs later. So if you defer fmt.Println(i) and then change i , the deferred call still prints the old value.
3️⃣ panic: Stopping Everything
panic halts normal execution, runs every pending defer as it unwinds the stack, and — if nothing catches it — crashes the program with a stack trace and exit code 2. Use it sparingly: a missing file or bad user input should be an error , not a panic.
4️⃣ recover: Catching a panic
recover only works inside a deferred function. When a panic is unwinding, a recover() call stops it and returns the value passed to panic ; if there's no active panic it returns nil . The idiomatic move is to turn the recovered value into a normal error via a named return.
🧩 Reorder Challenge
These lines build a function that recovers from a panic and reports it as an error. Put them in the correct order:
B, C, A, D, F, G, E, H. Open the function with a named return, then a deferred closure whose body checks recover() and assigns err ; close the if and the deferred func, then panic after the defer is registered, then close the function. The defer must be set up before the panic for recover to catch it.
🧠 Quick Recall
Predict the output before revealing the answer.
CBA — C runs immediately. The two deferred calls run at return in LIFO order: the last deferred ( B ) first, then A .
10 — the argument x was evaluated at the defer statement, when it was 10. Reassigning to 20 afterward doesn't change the already-captured value.
It does not crash, and prints nothing. The deferred recover() swallows the panic, so the program exits normally — but fmt.Println("end") is after the panic, so it never runs.
Pro Tips
- 💡 Defer cleanup right after you acquire a resource — f, _ := os.Open(...); defer f.Close() — so it's impossible to forget.
- 💡 Prefer errors over panics. Idiomatic Go returns an error for anything a caller might reasonably handle; panics are for programmer bugs.
- 💡 recover only works in a deferred function. Calling it directly in normal code always returns nil .
- 💡 Don't defer in a tight loop if it accumulates — deferred calls stack up until the function returns. Wrap the loop body in its own function if needed.
Common Errors (and the fix)
- recover returns nil unexpectedly — you called it outside a deferred function, or there was no active panic. It must be inside a defer during a panic.
- Deferred value is "stale" — arguments are evaluated at the defer statement. To capture a later value, wrap the call in a closure: defer func() { fmt.Println(x) } () .
- Resource leak despite defer — you deferred inside a loop that runs many times; the closes pile up until the function returns. Move the body into a helper function.
- Using panic for ordinary errors — callers can't easily handle a panic. Return an error instead so the caller can decide what to do.
- "defer fmt.Println used as value" — you wrote a method value instead of a call. defer needs a function call , including the parentheses.
Frequently Asked Questions
Q: When should I panic instead of returning an error?
Almost never in library code. Panic when the program has reached a state that should be impossible — a nil that the type system says can't be nil, an unreachable branch. For anything a caller might handle (bad input, missing file), return an error .
Modern Go has made defer very cheap, fast enough for virtually all code. Only in extremely hot loops measured to be a bottleneck might you hand-roll the cleanup instead.
Q: Can recover catch a panic from another goroutine?
No. recover only catches panics in its own goroutine. Every goroutine that might panic needs its own deferred recover, or an unhandled panic there will crash the whole program.
A common pattern is a deferred recover around each request handler so that one panicking request returns a 500 instead of taking down the entire server. The standard net/http server does exactly this for you.
Mini-Challenge: Crash-Proof Runner
No blanks — just a brief. Build a safeRun helper that runs any function but survives a panic via a deferred recover. This is exactly how production servers isolate one failing task from the rest.
🎉 Lesson Complete!
- ✅ defer runs cleanup when the function returns, in LIFO order
- ✅ Deferred arguments are evaluated immediately , the call runs later
- ✅ panic aborts, runs defers, and crashes if not recovered
- ✅ recover (inside a defer) stops a panic and returns its value
- ✅ Turn a recovered panic into a clean error with a named return
- ✅ Prefer error values; reserve panic for impossible states
- ✅ Next lesson: Error Wrapping with errors.Is/As and %w
Practice quiz
When does a deferred call run?
- Immediately
- At program exit
- When the surrounding function returns
- Never
Answer: When the surrounding function returns. defer schedules a call to run when the surrounding function returns, by any path including a panic.
In what order do multiple defers run?
- Last-in, first-out (LIFO)
- First-in, first-out
- Random
- Alphabetical
Answer: Last-in, first-out (LIFO). Deferred calls run in last-in, first-out order, like a stack.
What does this print: defer fmt.Print("A"); defer fmt.Print("B"); fmt.Print("C")?
- ABC
- CAB
- BCA
- CBA
Answer: CBA. C runs first immediately; then the defers run LIFO: B (last deferred) then A — giving CBA.
When are a deferred call's arguments evaluated?
- When the function returns
- Immediately, at the defer statement
- Never
- On the first panic
Answer: Immediately, at the defer statement. A deferred call's arguments are evaluated immediately at the defer statement, even though the call runs later.
x := 10; defer fmt.Println(x); x = 20 — what does the deferred line print?
- 10
- 20
- 0
- nothing
Answer: 10. The argument x was captured at the defer statement when it was 10; reassigning to 20 afterward doesn't change it.
What does panic do?
- Returns an error
- Pauses the program
- Aborts, runs pending defers while unwinding, then crashes if not recovered
- Logs a warning
Answer: Aborts, runs pending defers while unwinding, then crashes if not recovered. panic halts normal flow, runs deferred calls as the stack unwinds, and crashes the program unless something recovers.
Where must recover() be called to actually stop a panic?
- Anywhere in the function
- Inside a deferred function
- In main only
- Before the panic
Answer: Inside a deferred function. recover only works inside a deferred function; called directly in normal code it always returns nil.
What does recover() return when there is NO active panic?
- An error
- false
- It panics
- nil
Answer: nil. With no active panic, recover() returns nil.
func main() { defer func(){ recover() }(); panic("oops"); fmt.Println("end") } — what happens?
- Crashes
- Does not crash and prints nothing
- Prints end
- Prints oops
Answer: Does not crash and prints nothing. The deferred recover swallows the panic so it exits normally, but Println("end") is after the panic so never runs.
For an expected problem like bad input or a missing file, what should you use?
- panic
- os.Exit
- Return an error
- log.Fatal
Answer: Return an error. Idiomatic Go returns an error for problems a caller can handle; panic is reserved for impossible-state bugs.