I usually notice a missing cleanup path during review, which is a dignified way of saying after I wrote it.

Go’s defer lets me put cleanup beside acquisition:

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

The call runs when the surrounding function returns, including an early return. Several deferred calls run in last-in, first-out order. That order fits nested resources rather nicely.

Arguments are evaluated when the defer statement executes, not later. This matters in loops and when variables change:

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

The output is 2, 1, 0. It is a stack of already-prepared calls, not three promises to inspect i at the end.

I would not put defer in a hot inner loop without measuring it. This compiler and runtime are young, and convenience has a cost even when the syntax hides the invoice. A helper function can give each iteration its own cleanup boundary.

The larger win is boring correctness. Files close on every return, locks can be paired with unlocks, and the cleanup is visible before the function accumulates three branches. C’s goto out remains perfectly respectable, but defer makes the common case harder to omit. This note matches my current weekly snapshot; the surrounding library calls are still moving.