defer

Many resources require cleanup:

  • Files

  • Network connections

  • Database connections

  • Mutexes

Without careful cleanup, resources may leak.

Example:

file, err := os.Open("data.txt")
if err != nil {
    return err
}
 
// Use file
 
file.Close()

If the function returns early before file.Close(), the file remains open.


Using defer

file, err := os.Open("data.txt")
if err != nil {
    return err
}
 
defer file.Close()

defer schedules a function call to execute when the current function returns.

Cleanup is guaranteed regardless of how the function exits.


Basic Example

func main() {
    fmt.Println("A")
 
    defer fmt.Println("B")
 
    fmt.Println("C")
}

Output:

A
C
B

Deferred functions execute after the surrounding function finishes.


Multiple defer Statements

func main() {
    defer fmt.Println("1")
    defer fmt.Println("2")
    defer fmt.Println("3")
}

Output:

3
2
1

Deferred calls execute in Last In, First Out (LIFO) order.

Think of them as a stack.


Typical Uses

Closing files:

file, err := os.Open(...)
if err != nil {
    return err
}
 
defer file.Close()

Unlocking mutexes:

mu.Lock()
defer mu.Unlock()

Closing network connections:

conn, err := net.Dial(...)
if err != nil {
    return err
}
 
defer conn.Close()

Always Check Errors First

Correct:

file, err := os.Open(...)
if err != nil {
    return err
}
 
defer file.Close()

Incorrect:

file, err := os.Open(...)
 
defer file.Close()
 
if err != nil {
    return err
}

Only defer cleanup after confirming the resource was successfully acquired.


defer Captures Function Arguments Immediately

Example:

func main() {
    x := 10
 
    defer fmt.Println(x)
 
    x = 20
}

Output:

10

Reason:

The argument x is evaluated when the defer statement executes.

The function call itself is delayed, but its arguments are already fixed.


Anonymous Functions Behave Differently

func main() {
    x := 10
 
    defer func() {
        fmt.Println(x)
    }()
 
    x = 20
}

Output:

20

Why?

The anonymous function executes later and reads the variable when it runs.

It does not capture the value at the moment of defer.


Comparison

Argument captured immediately:

defer fmt.Println(x)

Reads variable later:

defer func() {
    fmt.Println(x)
}()

Mental model:

defer f(x)

        └── arguments evaluated immediately
 
defer func(){ ... }

                 └── function body executes later

defer Inside Loops

Example:

func main() {
    for i := 0; i < 3; i++ {
        defer fmt.Println(i)
    }
}

Output:

2
1
0

Each iteration evaluates i immediately.

Deferred calls execute later in reverse order.


defer Scope

Unlike C++ destructors, defer is function-scoped, not block-scoped.

Example:

func main() {
    {
        defer fmt.Println("Inner")
    }
 
    fmt.Println("Body")
}

Output:

Body
Inner

The deferred call waits until the function returns, not until the block ends.


Common Beginner Mistakes

Forgetting defer

Writing cleanup only at the end of the function can leak resources if the function returns early.


Deferring Before Checking Errors

Only defer cleanup after successful resource acquisition.


Confusing Deferred Arguments and Deferred Closures

These are different:

defer fmt.Println(x)

prints the value at the time of defer.

defer func() {
    fmt.Println(x)
}()

prints the value when the deferred function actually executes.


Comparison with C++

GoC++
defer file.Close()RAII cleanup
Cleanup at function exitCleanup at scope exit
LIFO deferred callsReverse destruction order
Explicit cleanup schedulingAutomatic destructor invocation

Both aim to guarantee cleanup, but they use different language mechanisms.


Session Summary

Topics covered:

  • defer

  • Resource cleanup

  • Execution order

  • LIFO behavior

  • Deferred arguments

  • Deferred anonymous functions

  • Function scope vs block scope

Practical skills gained:

  • Scheduling cleanup immediately after acquiring a resource

  • Predicting deferred execution order

  • Understanding when values are captured

  • Distinguishing deferred function calls from deferred closures


Key Takeaways

  • defer schedules a function to execute when the surrounding function returns.

  • Deferred calls execute in reverse order (LIFO).

  • Always place defer immediately after successful resource acquisition.

  • defer f(x) evaluates arguments immediately.

  • defer func(){...}() evaluates the function body later.

  • defer is function-scoped, not block-scoped.


Next Topics

  • File I/O
  • Reading and writing files
  • JSON encoding and decoding
  • Working with the standard library
  • Go Learning - Session 9