Go Defer

The defer keyword schedules a function call to run just before the surrounding function returns. No matter how the function exits — through a normal return, an early return, or even a panic — deferred calls always run. This makes defer ideal for cleanup tasks like closing files, releasing locks, or logging exit points.

Basic Defer

package main

import "fmt"

func main() {
    defer fmt.Println("World") // scheduled, runs last
    fmt.Println("Hello")
}

Output:

Hello
World

Even though defer fmt.Println("World") appears first in the code, it runs after fmt.Println("Hello") because defer waits until the function is about to return.

Defer Execution Flow

func main() {
    defer fmt.Println("3rd – deferred")   ← scheduled 3rd, runs 3rd
    defer fmt.Println("2nd – deferred")   ← scheduled 2nd, runs 2nd
    defer fmt.Println("1st – deferred")   ← scheduled 1st, runs 1st
    fmt.Println("Normal execution")
}

Execution order:
1. "Normal execution"
2. "1st – deferred"   ← LIFO: last in, first out
3. "2nd – deferred"
4. "3rd – deferred"

Multiple deferred calls execute in LIFO order — Last In, First Out. The last defer scheduled runs first.

LIFO Stack Diagram

Defer stack (like a stack of plates):

defer C  ← pushed last
defer B
defer A  ← pushed first

When function returns:
  C runs first  (last in, first out)
  B runs second
  A runs last

Defer for Cleanup – Closing a File

The most common real-world use of defer is ensuring a resource is released after it is opened, regardless of what happens in between.

package main

import (
    "fmt"
    "os"
)

func readFile(name string) {
    file, err := os.Open(name)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    defer file.Close() // always runs before readFile returns

    fmt.Println("File opened:", name)
    // ... read file contents ...
}

func main() {
    readFile("notes.txt")
}

Placing defer file.Close() immediately after opening the file guarantees it closes, even if the function returns early or panics.

Defer Arguments Are Evaluated Immediately

The arguments passed to a deferred function are evaluated at the moment defer runs, not when the deferred call executes.

package main

import "fmt"

func main() {
    x := 10
    defer fmt.Println("Deferred x:", x) // x is captured as 10 here

    x = 99
    fmt.Println("Current x:", x)
}

Output:

Current x: 99
Deferred x: 10

The deferred call captured x = 10 when the defer line ran, even though x changed to 99 afterward.

Common Uses of Defer

Use CaseExample
Close a filedefer file.Close()
Unlock a mutexdefer mu.Unlock()
Close a database connectiondefer db.Close()
Log function exitdefer fmt.Println("function done")
Recover from panicdefer recover()

Key Points

  • Deferred calls run just before the surrounding function returns
  • Multiple defers execute in LIFO order — last deferred runs first
  • Arguments to deferred calls are evaluated when the defer line executes, not when the call runs
  • Use defer for cleanup tasks to guarantee they always run, even on error paths

Leave a Comment