In Go, why is `defer` placed on the line right after the call that acquires a resource?
answer
- acquire and release read as one unit
- later returns get it for free
- the error check sits between them
- never release what you did not acquire
basics
~20 sPutting the release right below the acquisition lets a reader check the pair at a glance, and it covers every exit added below. It belongs after the error check, so it never releases what was never acquired.
solid answer
~40 sThe idiom is three lines: acquire, check the error, then `defer` the release. Adjacency is the readability half - a reader sees `rows, err := ...` and `defer rows.Close()` together and never has to scan the rest of the function to learn whether the resource is freed. Covering every exit is the correctness half: any `return` added later, anywhere below, still releases, which is precisely what a function full of early returns makes hard to get right by hand. The ordering matters as much as the adjacency. Register the deferred release only once you know the acquisition succeeded, because on the failure path the value is usually nil or half-built, so releasing it is at best pointless and for many types a panic.
code
go · 11 linesfunc (s *server) handleListUsers(w http.ResponseWriter, r *http.Request) {
rows, err := s.db.QueryContext(r.Context(), "SELECT id, name FROM users")
if err != nil {
http.Error(w, "query failed", http.StatusInternalServerError)
return
}
defer rows.Close() // after the check: on the failure path rows is nil
for rows.Next() {
// scan and accumulate
}
}go deeper
Memorise the three-line shape - acquire, check the error, defer the release - and be able to point at the middle line and say why the check belongs there rather than after.
Explain what the placement guarantees: every exit below it, including ones a later edit adds, and why registering above the check can call a release on a nil or half-built value.
Bring the ownership question. Say who acquires, who releases, what happens when the resource crosses a function boundary or a loop, and how a signature can make that obvious instead of implied.
Frame it as an expectation you set for a package other teams import: whether constructors hand back a cleanup func, and what you promise about resources when a call returns an error.
## The three-line shape A deferred call is a call registered now and executed when the surrounding function returns. The idiom this leaf is about is where that registration goes: ``` rows, err := s.db.QueryContext(ctx, q, id) // 1. acquire if err != nil { // 2. check return err } defer rows.Close() // 3. release, registered here ``` Three things follow from that placement, and a good answer names all three. ## 1. The pair is legible in one glance Acquisition and release are one decision, and putting them on adjacent lines lets a reader verify that decision without reading the rest of the function. The alternative - releasing at the bottom, next to the return - forces the reader to scroll to the end of the function and then check every other exit path to confirm the release is not skipped. In a function with early returns there are several such paths, and "is this closed on all of them?" is exactly the question a reviewer should not have to answer manually. This is why the convention belongs with flow shape rather than with cleanup semantics: it is a rule about where a line goes so that a human can read it. ## 2. It covers exits that do not exist yet The deferred call runs on every return below its registration - the ones written today and the ones added in six months. That is the part that survives maintenance. A future edit that inserts a guard clause in the middle of the function cannot leak the resource, because the author of that edit does not have to remember anything. Manual release at each exit has the opposite property: it is correct exactly until someone adds an exit. The same holds for the unusual exits. If the code between the acquisition and the return panics, the deferred release still runs while the stack unwinds, so a resource is not stranded by a bug elsewhere in the function. ## 3. It goes after the error check, never before This is the ordering constraint, and it is the part candidates get backwards. Registering the release above the check means it also runs on the failure path, where the value you are releasing was never acquired. Depending on the type, that is a no-op, an error you ignore, or a nil dereference that panics - and the third case turns a handled failure into a crash. There is no benefit to compensate: if the acquisition failed, there is nothing to release. So the check sits between the two lines, and the shape reads as a sentence: get it; if that failed, leave; otherwise arrange to give it back. ## The same shape for a lock ``` func (c *cache) get(k string) (string, bool) { c.mu.RLock() defer c.mu.RUnlock() v, ok := c.m[k] return v, ok } ``` Here there is no error to check, so the release is registered on the very next line. The value is the same: the reader sees both halves at once, and every path out of the method - including one added later, including a panic - unlocks. Note what it does not buy: a `sync.Mutex` is not reentrant, and deferring the unlock does not change that. A method that takes the lock and then calls another method that takes the same lock deadlocks regardless of where the `defer` sits. ## Ownership across a function boundary The rule assumes the function that acquires is the function that owns the lifetime. When it is not - a helper opens something and hands it back - the helper must **not** defer the release, because it would run at the helper's return and the caller would receive a dead resource. The caller acquires ownership and writes the `defer` itself, after checking the helper's error. Making that obvious is a design job for the signature: return the resource itself, or return a `func()` the caller defers. ## The loop caveat A deferred call is tied to the enclosing *function*, not to the enclosing block, so a loop body that acquires a resource per iteration and defers the release per iteration accumulates every one of them until the whole loop finishes and the function returns. Over a large input that is a resource leak with a slow fuse. The fix is a placement fix rather than an exception to the rule: move the loop body into its own small function that acquires, defers and returns, and call it once per iteration. Each release then happens at the end of that call. ## What a reviewer looks for Scanning a diff, three things are cheap to check and worth checking every time: is there a release for each acquisition, is it on the line after the check, and is the acquiring function the one that owns the lifetime. All three are visible without reading the body in between, which is the entire point of the placement.
- Where does the deferred release go when a helper performs the acquisition and returns the resource?In the caller. If the helper deferred it, the release would run when the helper returns and the caller would get a dead resource. The caller checks the helper's error, then defers. Make the ownership visible in the signature: return the resource itself, or return a `func()` cleanup the caller is expected to defer, so nobody has to guess.
- A loop body acquires a resource on each iteration - where should its release go?Not in the loop body. A deferred call is tied to the enclosing function, so registering one per iteration holds every resource until the whole function returns. Move the body into a small function that acquires, defers and returns, then call it once per iteration; each release then happens at the end of that call, which is what the loop actually wants.
- Is `defer` worth it in a function that has exactly one return?Usually yes. It costs almost nothing, it stays correct through the next edit that adds a guard clause or an early return, and it still runs if the code in between panics. The place it does not fit is a per-iteration acquisition inside a hot loop, and there the answer is to restructure into a function rather than to release by hand.
It is the habit of putting your keys in your pocket the moment you pick them up, rather than deciding at each of the four doors out of the building whether you still have them.
saying these in an interview costs you the question
- Registers the release before checking the acquisition error
- Puts the release at the bottom of the function beside the return
- Thinks a deferred call runs at the end of the enclosing block
- Defers inside a loop body and expects a per-iteration release
- Believes deferring the unlock makes a mutex reentrant