Start learning free

LESSON 50 OF 338 · FUNCTIONS I

Defer in Go: Basics, Order and Arguments

Lesson Read the full explanation

Go has a keyword for "do this later, no matter what": defer. Put defer in front of a function call and the call does not run now — it is scheduled to run when the surrounding function returns. This is how real Go code guarantees cleanup happens, even when the function exits early:

func readConfig() {
    f, _ := os.Open("config.txt")
    defer f.Close()   // scheduled NOW, runs when readConfig returns
    // ... use f freely; it is guaranteed to be closed at the end
}

Three rules govern defer, and all three surprise beginners:

  • Rule 1 — runs at function end. The deferred call fires when the function returns, not where the defer line sits.
  • Rule 2 — LIFO order. Several defers stack up like plates: the last one deferred runs first.
  • Rule 3 — arguments are evaluated at defer time. The call is delayed, but its arguments are computed immediately and frozen — a snapshot.

The trap:

First, defer must be given a real function call, parentheses and all. Forget them and the compiler stops you with exactly this error:

./prog.go:6:11: expression in defer must be function call

The subtler trap is Rule 3. Beginners expect a deferred print to see the variable's final value. It doesn't — the argument was snapshotted the moment the defer line ran:

n := 1
defer fmt.Println("n is", n)   // n is evaluated NOW: snapshot = 1
n = 99
// function returns here... and prints: n is 1   (not 99!)

Real bugs come from this: logging a "final" status that is actually stale, or closing the wrong file in a loop. Learn to predict the snapshot and defer becomes your most reliable cleanup tool.


Assignment
Follow the Coding Tutorial to schedule three deferred prints around a mutating variable, so the printed order proves all three rules at once:

  • Declare count := 1 and defer fmt.Println("deferred: count was", count).
  • Set count = 2, then defer the same print again.
  • Set count = 3, then defer the same print a third time.
  • Finally print fmt.Println("lesson body done, count =", count) — an ordinary, immediate call.

The exact expected output is:

lesson body done, count = 3
deferred: count was 3
deferred: count was 2
deferred: count was 1

Hint
Read the output against the rules: the body line prints first (Rule 1 — defers wait for the end), the deferred lines come out 3, 2, 1 (Rule 2 — LIFO), and each shows the value count had at its own defer line, not the final 3 (Rule 3 — snapshot).


STEP 1 OF 7
    Ctrl + Enter to check

    GO · FUNCTIONS I

    Defer Basics in Go, explained

    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
    
    1. Declare count := 1. Every deferred print will save a copy of this value at its own moment.
    2. Defer the first print. defer fmt.Println("deferred: count was", count) saves 1.
    3. Set count = 2. Use plain =, since the variable exists. The first deferred call already holds 1 and is not affected.
    4. Defer the same print again. It saves 2.
    5. Set count = 3 and defer once more. The third call saves 3.
    6. Print the body line. fmt.Println("lesson body done, count =", count) is an ordinary call, so it prints first, with 3.

    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 of 3 3 3 would mean the arguments were read late, and they are not.
    • Forgetting the parentheses. defer fmt.Println does not compile.
    • Using := for the reassignments. count := 2 in 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) for i from 0 to 2 prints loop 2, loop 1, loop 0 at the end.

    Key takeaways

    • defer postpones a call until the surrounding function returns.
    • Deferred calls run in last-in, first-out order.
    • Arguments are evaluated when the defer line runs, not when the call does.
    • defer needs a function call with parentheses.
    Keep going with GoAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free