Explain defer — how does it work, and what are common pitfalls?
Quick Answer
defer schedules a function call to run after the enclosing function returns, in LIFO order if multiple defers are registered. It's most commonly used for cleanup, like closing a file or unlocking a mutex, guaranteeing that cleanup runs even if the function returns early or panics. Two common pitfalls: defer arguments are evaluated immediately, when the defer statement runs, not when the deferred call actually executes — so defer fmt.Println(x) captures x's value at that point, not its final value; and deferring inside a loop accumulates all deferred calls until the enclosing function returns, not the loop iteration, which can exhaust resources like file handles if you're opening many files in a loop.
Detailed Answer
defer is Go's answer to finally blocks, but its exact evaluation timing surprises newcomers.
Basic usage
func readFile(path string) error {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close() // runs when readFile returns, no matter which path
// ... use f ...
return nil
}
LIFO ordering
func demo() {
defer fmt.Println("1")
defer fmt.Println("2")
defer fmt.Println("3")
}
// Output: 3
// 2
// 1
Pitfall: arguments evaluated immediately
func demo() {
x := 1
defer fmt.Println("x was:", x) // "x" captured as 1 right now
x = 2
}
// Output: "x was: 1", not 2
If you need the final value, defer a closure instead:
defer func() { fmt.Println("x is now:", x) }() // reads x at call time
Pitfall: defer in a loop
func processFiles(paths []string) error {
for _, p := range paths {
f, err := os.Open(p)
if err != nil {
return err
}
defer f.Close() // BUG: all files stay open until processFiles returns
}
return nil
}
With hundreds of files, this can exhaust the OS's file descriptor limit before processFiles ever returns. Fix it by wrapping the loop body in its own function, so each defer fires per iteration:
func processOne(p string) error {
f, err := os.Open(p)
if err != nil {
return err
}
defer f.Close()
// ... use f ...
return nil
}