What defer does
Go has a keyword for "do this later, whatever happens": defer. Put it in front of a function call and the call does not run at that line. Go schedules it to run when the surrounding function returns.
The main use is cleanup that must not be forgotten, even if the function leaves early:
func readConfig() {
f, _ := os.Open("config.txt")
defer f.Close() // scheduled now, runs when readConfig returns
// use f freely; it is closed at the end
}
Placing defer f.Close() right after opening the file keeps the opening and the closing side by side, so the closing is hard to forget. Three rules decide how defer behaves, and each one surprises beginners.
Rule 1: it runs when the function returns
The deferred call fires at the end of the function, not at the line where defer sits. Everything written after the defer line runs first.
Rule 2: last in, first out
Several deferred calls stack up like plates. The last one deferred is the first to run. Defer three calls in order 1, 2, 3 and they run as 3, 2, 1. This reverse order is useful for cleanup: resources acquired later are released earlier.
Rule 3: arguments are evaluated at defer time
The call is delayed, but its arguments are computed immediately and kept. A later change to a variable does not reach into the saved argument:
n := 1
defer fmt.Println("n is", n) // n is evaluated now: the saved value is 1
n = 99
// when the function returns, this prints: n is 1
Beginners expect to see 99. The argument was saved the moment the defer line ran. If you do want the latest value, defer a function literal that reads the variable when it runs:
n := 1
defer func() { fmt.Println("closure sees", n) }()
n = 99
Run inside a function, this prints closure sees 99, because the closure looks at n at the end, not at the time of the defer. A plain defer fmt.Println("n is", n) placed before n = 99 would still print 1: the argument is saved, the closure is not.
The trap with the parentheses
defer needs a real function call, parentheses included. Writing defer fmt.Println without them is a compile error:
expression in defer must be function call
The lesson's exercise
The exercise schedules three deferred prints around a changing variable, so the output shows all three rules at once. The expected output is:
lesson body done, count = 3
deferred: count was 3
deferred: count was 2
deferred: count was 1
- Declare
count := 1. Every deferred print will save a copy of this value at its own moment. - Defer the first print.
defer fmt.Println("deferred: count was", count)saves1. - Set
count = 2. Use plain=, since the variable exists. The first deferred call already holds1and is not affected. - Defer the same print again. It saves
2. - Set
count = 3and defer once more. The third call saves3. - Print the body line.
fmt.Println("lesson body done, count =", count)is an ordinary call, so it prints first, with3.
Then the function ends and the deferred calls run from last to first: 3, 2, 1. The order proves Rule 2, the early body line proves Rule 1, and the values 2 and 1 after the final assignment prove Rule 3.
The solution in Go
count := 1
defer fmt.Println("deferred: count was", count)
count = 2
defer fmt.Println("deferred: count was", count)
count = 3
defer fmt.Println("deferred: count was", count)
fmt.Println("lesson body done, count =", count)
Common mistakes
- Expecting the final value. A deferred call with arguments prints the values from the moment of the
defer. Output of3 3 3would mean the arguments were read late, and they are not. - Forgetting the parentheses.
defer fmt.Printlndoes not compile. - Using
:=for the reassignments.count := 2in the same scope fails because there is no new variable. - Deferring in a loop without thinking. Each iteration adds one more deferred call, and they all wait until the function returns, in reverse order. A loop deferring
fmt.Println("loop", i)forifrom 0 to 2 printsloop 2,loop 1,loop 0at the end.
Key takeaways
deferpostpones a call until the surrounding function returns.- Deferred calls run in last-in, first-out order.
- Arguments are evaluated when the
deferline runs, not when the call does. deferneeds a function call with parentheses.