defer
A defer statement schedules a function call to run when the surrounding function returns — however it returns, even through an early return or a panic. It's Go's way of guaranteeing clean-up: closing files, unlocking mutexes, finishing timers. This lesson covers how deferred calls are ordered, when their arguments are evaluated, common uses, and the pitfalls.
Running code on the way out
package main import "fmt" func main() { fmt.Println("start") defer fmt.Println("deferred: runs last") fmt.Println("working...") }
start working... deferred: runs last
The deferred call waits until main is about to return. The great advantage is placement: the clean-up sits right next to the thing that needs cleaning up, so you can't forget it when you add another return later.
Closing resources
The classic pattern: acquire a resource, check the error, then immediately defer its release:
package main import ( "bufio" "fmt" "os" ) func countLines(path string) (int, error) { f, err := os.Open(path) if err != nil { return 0, err } defer f.Close() // runs on every return below n := 0 sc := bufio.NewScanner(f) for sc.Scan() { n++ } return n, sc.Err() } func main() { os.WriteFile("notes.txt", []byte("one\ntwo\nthree\n"), 0o644) fmt.Println(countLines("notes.txt")) fmt.Println(countLines("missing.txt")) }
3 <nil> 0 open missing.txt: no such file or directory
Defer only after checking the error: if os.Open failed, there's no file to close.
Last in, first out
A function can defer several calls. They run in reverse order — like a stack — which is exactly right when later resources depend on earlier ones:
package main import "fmt" func main() { for i := range 3 { defer fmt.Println("deferred", i) } fmt.Println("loop done") }
loop done deferred 2 deferred 1 deferred 0
Arguments are evaluated immediately
The function call is delayed, but its arguments are worked out when the defer statement runs. To use a variable's value at return time, defer a closure instead:
package main import "fmt" func main() { status := "starting" defer fmt.Println("argument captured at defer time:", status) defer func() { fmt.Println("closure sees the final value:", status) }() status = "finished" }
closure sees the final value: finished argument captured at defer time: starting
Measuring how long a function takes
Combining both rules gives a neat timing helper: trace runs immediately (it's an argument) and returns a function that runs at the end:
package main import ( "fmt" "time" ) func trace(name string) func() { start := time.Now() fmt.Println("enter", name) return func() { elapsed := time.Since(start) fmt.Println("exit", name, elapsed >= 20*time.Millisecond) } } func slowWork() { defer trace("slowWork")() time.Sleep(20 * time.Millisecond) } func main() { slowWork() }
enter slowWork exit slowWork true
Note the two pairs of parentheses in defer trace("slowWork")(): the first call happens now, the returned function is deferred. (Real code would print elapsed; here it prints a comparison so the output is the same every run.)
Changing results with defer
A deferred closure runs after the return statement has set the results, but before the caller receives them. With named results it can inspect and change them — commonly to add context to an error:
package main import ( "errors" "fmt" ) func loadConfig(name string) (err error) { defer func() { if err != nil { err = fmt.Errorf("loading config %q: %w", name, err) } }() if name == "" { return errors.New("empty name") } return nil } func main() { fmt.Println(loadConfig("app.toml")) fmt.Println(loadConfig("")) }
<nil> loading config "": empty name
Pitfall: defer in a loop
Deferred calls run when the function returns, not at the end of each loop iteration. Deferring Close inside a loop that opens thousands of files keeps them all open until the end. Move the body into its own function so each defer runs per item:
package main import ( "fmt" "os" ) func size(path string) (int64, error) { f, err := os.Open(path) if err != nil { return 0, err } defer f.Close() // closed when size returns, once per file info, err := f.Stat() if err != nil { return 0, err } return info.Size(), nil } func main() { os.WriteFile("a.txt", []byte("hello"), 0o644) os.WriteFile("b.txt", []byte("hello, world"), 0o644) for _, p := range []string{"a.txt", "b.txt"} { n, err := size(p) fmt.Println(p, n, err) } }
a.txt 5 <nil> b.txt 12 <nil>
Pitfall: ignored errors from Close
defer f.Close() throws away Close's error. That's fine for files you only read. When writing, Close can report that data failed to reach the disk, so check it — for example with the named-result technique above, or by calling Close explicitly at the end and returning its error.
Try it yourself
Predict the output, then run it. Which line prints first, and why?
evaluating A body evaluating B deferred closure: B deferred: A