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

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.

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.

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.

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.

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.

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.