Skip to content

Beginner · Control flow and functions · topic 10 of 46 · 30 min

defer: clean up at the end

Explanation

defer schedules a function call to run when the surrounding function returns, whether it returns normally or because of a panic. Deferred calls run in reverse order (last in, first out), and their arguments are worked out immediately, when the defer line runs. It is the Go way to close files, unlock locks and finish clean-up.

The problem defer solves

When you open a file or a database connection, you must close it when you are done, on every path out of the function: the normal end, every early return and every error. Writing Close() before each return is easy to forget. defer puts the clean-up right next to the setup.

go
func readConfig(path string) ([]byte, error) {
	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer f.Close() // runs when readConfig returns, on any path

	return io.ReadAll(f)
}
Open, check the error, then defer the close on the very next line. This is the standard Go pattern.
Think of it like this: defer is like the note you stick on the door when you enter a hostel room: "switch off the fan when you leave". However you leave, by the door or because the warden calls you, the note reminds you.

Rule 1: deferred calls run last-in, first-out

go
func main() {
	defer fmt.Println("1st deferred")
	defer fmt.Println("2nd deferred")
	fmt.Println("main body")
}
// main body
// 2nd deferred
// 1st deferred
Like a stack of plates: the last plate put on is the first taken off.

Rule 2: arguments are evaluated immediately

go
x := 1
defer fmt.Println("deferred x =", x)
x = 2
fmt.Println("now x =", x)
// now x = 2
// deferred x = 1
The value of x is copied when the defer line runs, not when the call happens.
go
x := 1
defer func() { fmt.Println("closure sees", x) }()
x = 2
// closure sees 2
A deferred closure reads x when it finally runs, so it sees the latest value.

Rule 3: deferred functions can change named results

go
func process() (err error) {
	defer func() {
		if err != nil {
			err = fmt.Errorf("process failed: %w", err)
		}
	}()
	return errors.New("disk full")
}
// process() returns "process failed: disk full"
The deferred function runs after return sets err, and can wrap or change it. Used for adding context to errors and for recover (module 4).

Common uses

SetupDeferred clean-up
f, err := os.Open(p)defer f.Close()
mu.Lock()defer mu.Unlock()
resp, err := http.Get(u)defer resp.Body.Close()
rows, err := db.Query(...)defer rows.Close()
ctx, cancel := context.WithTimeout(...)defer cancel()
start := time.Now()defer func() { log.Println(time.Since(start)) }()

Mistakes to avoid

  • Deferring before checking the error: f, err := os.Open(p); defer f.Close(); if err != nil ... If Open failed, f is nil and Close may panic. Check err first, then defer.
  • defer inside a long loop: deferred calls only run when the FUNCTION returns, not at the end of each loop round. Opening 10,000 files in a loop with defer f.Close() keeps all of them open until the end. Move the loop body into its own function.
  • Expecting defer to run after os.Exit: os.Exit ends the program immediately and skips all deferred calls. log.Fatal calls os.Exit too.
go
for _, path := range paths {
	if err := processFile(path); err != nil { // each file opened AND closed
		return err
	}
}

func processFile(path string) error {
	f, err := os.Open(path)
	if err != nil {
		return err
	}
	defer f.Close()
	// ... work with f ...
	return nil
}
The fix for defer in a loop: a helper function, so each defer runs at the end of each call.
Tip: defer has a tiny cost, but in normal code it is negligible. Prefer defer for clean-up; correctness beats saving nanoseconds.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

defer: clean up at the end · 1/7

defer

defer is Go's tool for clean-up. It schedules a call to run when the function returns.

Prefer a video by a teacher? Find free videos on “golang defer explained order arguments evaluated” on YouTube ↗

In real life: Timing a payment API call

A payment service logs how long each call to the bank takes, so slow banks can be spotted. One deferred line at the top of the function records the duration on every path, success or error, without repeating the timing code before each return.

go
package main

import (
	"fmt"
	"time"
)

func callBank(ref string) error {
	start := time.Now()
	defer func() {
		fmt.Println(ref, "took", time.Since(start).Round(time.Millisecond))
	}()

	time.Sleep(120 * time.Millisecond) // pretend to call the bank
	return nil
}

func main() {
	callBank("UPI-4821")
}
Prints something like UPI-4821 took 120ms. The exact time varies.

Practice (0/4 done)

Task 1 · Question

What does this print? for i := 0; i < 3; i++ { defer fmt.Print(i, " ") }

Task 2 · Question

What does f() return? func f() (n int) { defer func() { n *= 2 }() return 5 }

Task 3 · Question

Which line is the bug? 1: f, err := os.Open(path) 2: defer f.Close() 3: if err != nil { 4: return err 5: }

Task 4 · Practice

Write a function that prints "start", defers printing "cleanup 1" and "cleanup 2", then prints "working". Predict the output before running it, then run it to check.

Quick quiz

Question 1

1.When does a deferred call run?

Question 2

2.x := 1; defer fmt.Println(x); x = 5. What is printed at the end?

Question 3

3.Do deferred calls run after os.Exit(1)?

Question 4

4.What is the usual way to make sure a mutex is unlocked?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

When does a deferred call run?

Answer: When the surrounding function returns

Deferred calls run as the function returns, including during a panic.

x := 1; defer fmt.Println(x); x = 5. What is printed at the end?

Answer: 1

Arguments are evaluated when the defer statement runs, so 1 is printed.

Do deferred calls run after os.Exit(1)?

Answer: No, os.Exit ends the program immediately

os.Exit skips deferred calls; so does log.Fatal, which calls os.Exit.

What is the usual way to make sure a mutex is unlocked?

Answer: mu.Lock(); defer mu.Unlock()

Lock, then immediately defer Unlock, so every return path unlocks.

What runs when a function panics?Related

Answer: Its deferred calls, then the callers' deferred calls

The panic unwinds the stack, running deferred calls as it goes.

From panic and recover: only for the truly unexpected

Before Go 1.22, what did for i := 0; i < 3; i++ { defer func() { fmt.Print(i) }() } print?Related

Answer: 333

All closures shared one i, which was 3 when they ran. Since Go 1.22 it prints 210.

From Go language interview questions

What is deferred execution?Related

Answer: A query runs only when its results are enumerated

The query is a recipe until enumerated.

From LINQ in depth: grouping, joins, deferred execution, IQueryable

How does a Go function usually report failure?Related

Answer: By returning an error value as the last result

Go functions return an error as the last result; nil means success.

From Functions and multiple return values

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. This is the last topic of “Control flow and functions”. The module exam comes next.
  2. Module 2 exam: Control flow and functions · 13 questions · 20 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, Collections and structs. 46 topics left in the course before the final exam.

← Previous: Variadic functions, function values and closures