Last Updated: May 22, 2026
defer schedules a function call to run when the surrounding function returns. It's how Go code closes files, unlocks locks, and releases reserved stock without scattering cleanup logic across every return path. The mechanics are small (one keyword, one rule for argument evaluation, one rule for execution order), but the consequences run deep through real Go code.
defer Actually DoesWhen the runtime hits a defer statement, it records the call (function plus arguments) on a per-goroutine stack and moves on. When the surrounding function returns, the runtime pops that stack and runs each call. This happens whether the function returns normally, returns early, or panics.
Even though the defer line sits in the middle of the function, its output appears last. The call was queued at that line and executed once placeOrder finished its work. Write the cleanup next to the setup, then trust the runtime to call it at the right moment.
The most common production use pairs defer with os.Open. Open the file, defer the close, do the work, return whenever you like:
The defer f.Close() sits one line after the successful open, so the pairing is obvious. Whether Fprintf succeeds or fails, the file gets closed before writeOrderLog returns. Without defer, you'd have to call f.Close() on every return path, and one missing call is a leaked file descriptor.
The same pattern shows up for mutexes: acquire a lock, defer the unlock, do the work.
The takeaway is that defer keeps acquire/release pairs visually adjacent so neither side gets forgotten.
Multiple defer statements stack up. They run in last-in, first-out order, the opposite of the order they were written. Each defer pushes onto a stack, and the return pops the calls one by one.
This matches how real cleanup works. Resources get released in reverse of how they were acquired, since later setup often depends on earlier setup.
The diagram shows the two phases. Each defer statement pushes onto the stack. Once the function body finishes, the runtime pops the stack from the top, so the last defer runs first.
One rule about argument evaluation is easy to miss: arguments to a deferred call are evaluated at the `defer` statement, not when the call actually runs. The runtime captures the values at the point of defer and uses those captured values later.
The deferred call captured stockHold = 5 at the defer line. Reassigning stockHold = 0 after that has no effect on the queued call, because fmt.Println already received a copy of 5. The call runs at the end, but with the arguments frozen from earlier.
This becomes a classic pitfall inside a loop:
Each iteration evaluates i and queues a call with that specific value. The deferred calls then run in reverse, so 2, 1, 0. People expect 0 1 2 (forgetting LIFO) or 3 3 3 (confusing it with closure capture). Neither is right.
If you want closure-capture behavior instead, wrap the work in a function literal that reads the variable when it runs:
The function literal has no arguments to capture, so the rule technically still holds. The body reads i at execution time and sees the live value.
Cost: A deferred function literal that closes over variables forces those variables onto the heap. For tight loops, this can matter. If you don't need closure semantics, pass values as arguments.
When a function uses named return values, a deferred function can read and modify them before the caller sees the result. The deferred call runs after return has assigned the named variables but before control leaves the function.
The deferred function zeroes out final whenever err is non-nil, so callers can't accidentally use a partial value. This pattern works only because the returns are named and therefore addressable from inside the deferred call.
A panic is Go's way of bailing out of a function abnormally (an out-of-bounds slice index, a nil pointer dereference, or an explicit panic("...")). When that happens, the runtime unwinds the call stack and still runs every deferred call along the way.
The deferred prints still execute, even though the function is dying. This is what makes defer safe for cleanup: files get closed, locks get released, and stock holds get returned without scattering error handling everywhere. There's a tool called recover that can intercept a panic from inside a deferred function.
defer used to carry a noticeable overhead because each call recorded its data on the heap. Since Go 1.14, the compiler uses open-coded defer for the common case (a small number of defers, not inside a loop), which inlines the call and brings the cost close to a direct function call.
For almost all code, defer cost isn't worth thinking about. The one case where it can still matter is a defer inside a hot loop running millions of iterations. The fix is to hoist the loop body into a helper function so each defer is bounded to that helper's lifetime.