defer

defer lets you schedule a function call to run when the current function returns. The simplest way to see it is with plain prints:

func main() {
    fmt.Println("start")
    defer fmt.Println("cleanup")
    fmt.Println("working")
}

That prints:

start
working
cleanup

fmt.Println("cleanup") is queued when the defer line runs, but it does not execute yet. fmt.Println("working") runs next. Then main is about to return, Go notices the queued deferral, and fires it.

Why this matters in real code

The toy example is easy enough to follow, but the real value shows up when a function has several exit paths.

Note

This next example uses function syntax and file handling that get proper lessons later. You do not need the details yet. Focus on the repeated f.Close() calls and the fact that one return path forgets to close the file.

func process(path string) error {
    f, err := os.Open(path)
    if err != nil {
        return err
    }

    if err := readHeader(f); err != nil {
        f.Close()
        return err
    }

    if err := readBody(f); err != nil {
        return err         // oops, forgot to close
    }

    f.Close()
    return nil
}

Every early return needs its own f.Close(), and the moment someone adds a fourth return path, they forget one. That is what defer exists to remove. It schedules a call to run when the surrounding function returns, no matter how it returns: normal return, early return, or a panic partway through. Pair the release with the acquire once, and every exit path cleans up for free.

func process(path string) error {
    f, err := os.Open(path)
    if err != nil {
        return err
    }
    defer f.Close()

    if err := readHeader(f); err != nil {
        return err
    }
    if err := readBody(f); err != nil {
        return err
    }
    return nil
}

LIFO order with multiple defers

Defer more than one call and they run in reverse order. The last one deferred runs first:

defer fmt.Println("1")
defer fmt.Println("2")
defer fmt.Println("3")
// prints: 3, 2, 1

This reverse order is exactly what paired operations want. Open + close, lock + unlock, push + pop: you defer the close immediately after the open, so the close is queued in the right order even if more opens happen afterwards.

Arguments are captured at defer-time

This is the surprise that trips almost every Go beginner on their first defer. The arguments to the deferred call are evaluated when the defer line runs, not when the call eventually fires:

x := 10
defer fmt.Println("x was", x)   // captures x = 10 right now
x = 20
// when main returns, prints: x was 10

Reassigning x later does not affect the deferred print. Go evaluated x the moment it saw defer, stored the value 10, and used that stored value when the call fired later.

If you want the value at execution time instead, wrap the work in an anonymous function. The function itself is deferred, not its body, so the body runs later and reads x at that moment:

x := 10
defer func() { fmt.Println("x was", x) }()   // reads x when it fires
x = 20
// prints: x was 20

This uses closures, covered in the Functions chapter; for now it is enough to know the workaround exists.

Where defer shows up in real code

The file-open pattern above is the one you will see most often, but the shape is the same for anything that pairs with an earlier allocation: mu.Unlock() on mutexes, database connection cleanup, temporary file deletion, closing an HTTP response body. The File I/O and Concurrency chapters revisit defer in those contexts.

Defer right after acquire

The idiom is: the moment you acquire a resource (open a file, lock a mutex, start a span), defer its release on the very next line. Keeping the pair together visually makes it impossible to forget the release when you add new return paths to the function later.

defer in a loop queues up forever

A defer fires when the surrounding function returns, not when the current loop iteration ends. Queue a defer inside a for and every iteration adds to the queue, none of them firing until the function itself is about to return. Try this in the editor on this site:

for i := 0; i < 3; i++ {
    defer fmt.Println("iter", i)
}
fmt.Println("done")

It prints done first, then iter 2, iter 1, iter 0 all at the end. Now imagine each of those was f.Close() on a file you just opened: three files (or three thousand) staying open until the function exits. The fix is to lift the loop body into its own function, so the defer fires at each call's return. When you reach for defer inside a for, ask whether the body should really be a helper.

Task

Write main so the program prints exactly these four lines in this order:

start
working
cleanup 1
cleanup 2

Rules:

  • Do not write the print statements in the order they should appear. Use defer to schedule the two cleanup lines.
  • Print "start" first with fmt.Println.
  • Schedule "cleanup 2" with defer first, then schedule "cleanup 1" with defer second.
  • Print "working" after both defer statements.

If you remember that LIFO means the last defer runs first, the cleanup lines will fall into the right order automatically.

Expected output
start
working
cleanup 1
cleanup 2