defer, panic and recover

Go · Practical · 10 min

defer is the standard way to release resources, and knowing panic/recover tells you when not to use them.

A defer statement schedules a call to run when the surrounding function returns — normally, early, or by panic. Put it right after acquiring a resource so cleanup can't be forgotten:

f, err := os.Open(name)
if err != nil { return err }
defer f.Close()

Rules worth knowing:

  • Deferred calls run in last-in, first-out order.
  • The arguments are evaluated when the defer statement runs, not when the call happens: defer fmt.Println(i) prints the value i had at that moment.
  • Deferred closures can read and modify named results, which is how you can adjust the returned error after the fact.
  • defer is per function, not per block. Deferring inside a long loop piles up calls until the function returns — move the loop body into a helper function.

A panic stops normal execution, runs the deferred calls up the stack, and crashes the program with a stack trace if nothing stops it. Runtime errors (nil dereference, index out of range) panic, and you can call panic(v) yourself — but only for truly unrecoverable programmer errors or impossible states. Expected failures are errors, not panics.

recover() called inside a deferred function stops a panic and returns its value; elsewhere it returns nil. Use it at boundaries — an HTTP server recovers per request so one bad handler doesn't kill the process — and convert the panic into an error. A panic in a goroutine can only be recovered inside that goroutine.

Example

package main

import (
	"errors"
	"fmt"
)

func safeDiv(a, b int) (q int, err error) {
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("recovered: %v", r) // set named result
		}
	}()
	return a / b, nil
}

func main() {
	for i := range 3 {
		defer fmt.Print(i, " ") // runs at return: 2 1 0
	}
	_, err := safeDiv(1, 0)
	fmt.Println(err)                       // recovered: runtime error: integer divide by zero
	fmt.Println(errors.Unwrap(err) == nil) // true
}

Remember

  • defer cleanup right after acquiring the resource; runs LIFO
  • Deferred call arguments are evaluated immediately
  • panic for bugs, errors for expected failures; recover only in a deferred func

Watch out for

  • Deferring Close inside a loop over many files — they all stay open until the function returns.

Check yourself

  1. What does this print? `x := 1; defer fmt.Println(x); x = 2`

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: go.dev/blog/defer-panic-and-recover

Builds on

Unlocks

Nothing builds on this yet.