The defer keyword in Go offers a way to ensure that resources are properly released and that certain actions are performed, no matter how a function exits.

How to defer in Golang like a pro


The defer keyword in Go offers a way to ensure that resources are properly released and that certain actions are performed, no matter how a function exits.

The easiest way to learn about it is to look at lots of code, so lets do that!

How defer Works

When you use defer before a function call, Go schedules that function to run after the surrounding function returns. Deferred functions are pushed onto a stack and executed in last-in-first-out (LIFO) order.

Basic Example

package main

import "fmt"

func main() {
    fmt.Println("Function start")

    defer fmt.Println("First deferred")
    defer fmt.Println("Second deferred")

    fmt.Println("Function end")
}

Outputs:

Function start
Function end
Second deferred
First deferred

In this example, “Second deferred” is printed before “First deferred” because deferred calls are executed in reverse order of their deferment.

Practical Applications

Resource Cleanup

One of the most common uses of defer is to ensure that resources like files or network connections are properly closed.

package main

import (
    "bufio"
    "fmt"
    "os"
)

func readFile(filename string) {
    file, err := os.Open(filename)
    if err != nil {
        fmt.Println("Error opening file:", err)
        return
    }
    defer file.Close() // Ensures the file is closed when the function exits

    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        fmt.Println(scanner.Text())
    }
}

By deferring file.Close(), you guarantee that the file is closed regardless of how the function exits, even if an error occurs during file processing.

Mutex Locks

In concurrent programming, defer is useful for unlocking mutexes.

package main

import (
    "fmt"
    "sync"
)

var mu sync.Mutex

func updateSharedResource() {
    mu.Lock()
    defer mu.Unlock() // Ensures the mutex is unlocked when the function exits

    // Critical section
    fmt.Println("Updating shared resource")
}

This approach prevents deadlocks by making sure that the mutex is always unlocked.

Timing Function Execution

You can measure the execution time of a function using defer along with time tracking.

package main

import (
    "fmt"
    "time"
)

func longRunningFunction() {
    start := time.Now()
    defer func() {
        elapsed := time.Since(start)
        fmt.Println("Function took", elapsed)
    }()

    // Simulate long operation
    time.Sleep(2 * time.Second)
}

func main() {
    longRunningFunction()
}

Outputs:

Function took 2.000123456s

Important Details to Know About Defer

Evaluation of Deferred Function Arguments

Arguments to deferred functions are evaluated immediately, but the function call is executed later.

package main

import "fmt"

func main() {
    x := 10
    defer fmt.Println("Deferred:", x)
    x = 20
    fmt.Println("Current:", x)
}

Outputs:

Current: 20
Deferred: 10

Even though x is updated to 20, the deferred function captures the value of x at the time defer is called.

Modifying Return Values

Deferred functions can modify named return values in the surrounding function.

package main

import "fmt"

func compute() (result int) {
    defer func() {
        result *= 2
    }()
    result = 5
    return
}

func main() {
    fmt.Println("Result:", compute())
}

Outputs:

Result: 10

The deferred function modifies the result after it has been set but before it is returned.

Error Handling and Recovery

Using defer with recover(), you can handle panics gracefully.

package main

import "fmt"

func mayPanic() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered from:", r)
        }
    }()
    panic("An unexpected error occurred")
}

func main() {
    mayPanic()
    fmt.Println("Program continues")
}

Outputs:

Recovered from: An unexpected error occurred
Program continues

The deferred function checks if a panic has occurred and handles it, allowing the program to continue running.

Best Practices for defer in Go

  • Avoid Deferring in Loops: Deferring inside loops can lead to performance issues and resource leaks.
for i := 0; i < 1000; i++ {
    defer fmt.Println(i) // Not recommended
}
  • Use Anonymous Functions for Complex Logic: When you need more control, wrap your deferred calls in anonymous functions.
defer func() {
    // Complex deferred logic
}()
  • Be Mindful of Variable Scope: Since deferred functions execute after the surrounding function returns, ensure that variables used are accessible.

Wrapping Up

We hope you found this blog post on defer useful! If you did, consider joining our mailing list below, or checking out some more of our blog posts here: