Go defer

Summary: in this tutorial, you will learn how to use the Go defer keyword to defer the execution of a function until the surrounding function completes.

Introduction to Go defer keyword

The defer keyword schedules a function call to be executed after the surrounding function completes.

Here’s the syntax of the defer keyword:

func outer() {
    defer inner()
    // function body
    // ...
}Code language: Go (go)

In this syntax, the defer keyword schedules the inner() function to run at the outer() function exit.

Go will execute the outer() function body first and then run the inner() function.

It’s important to notice that Go always executes the inner() function regardless of how the outer() function returns. For example:

package main

import "fmt"

func exit() {
    fmt.Println("Bye!")
}

func sayHi() bool {
    defer exit()
    fmt.Println("Hi there")
    return true
    // always execute the exit function
    // exit()
}

func main() {
    sayHi()
}Code language: Go (go)

Output:

Hi there
Bye!Code language: Go (go)

How it works.

First, define the exit() function:

func exit() {
   fmt.Println("Bye!")
}Code language: Go (go)

Second, define the sayHi() function:

func sayHi() bool {
   defer exit()
   fmt.Println("Hi there")
   return true
   // always execute the exit function
   // exit()
}Code language: Go (go)

Inside the sayHi() function:

  • Defer the exit() function.
  • Display a message "Hi there" on the screen
  • Return true

Third, call the sayHi() function inside the main() function:

func main() {
   sayHi()
}Code language: Go (go)

After executing the sayHi() function, Go executes the exit() function.

Argument evaluation

Go evaluates arguments immediately at the defer statement but executes the function until the surrounding function completes. For example:

package main

import "fmt"

func exit(name string) {
    fmt.Println("Bye", name)
}

func sayHi(name string) {
    defer exit(name)
    name = "Alice"
    fmt.Println("Hi", name)
}

func main() {
    sayHi("Bob")
}
Code language: Go (go)

Output:

Hi Alice
Bye BobCode language: Go (go)

How it works.

First, define the exit() function that accepts a string argument:

func exit(name string) {
   fmt.Println("Bye", name)
}Code language: Go (go)

The exit() argument displays a message Bye name

Second, define the sayHi() function that also accepts a string argument:

func sayHi(name string) {
   defer exit(name)
   name = "Alice"
   fmt.Println("Hi", name)
}Code language: Go (go)

In the sayHi() function:

  • Defer the exit() function. Go evaluates the name argument in the first line of the sayHi() function.
  • Change the value of the name parameter to "Alice"
  • Display the Hi Alice message.

Third, call the sayHi() function with the “Bob” argument inside the main() function:

func main() {
   sayHi("Bob")
}Code language: Go (go)

Since Go evaluates the name argument at the first line of the sayHi function, the output shows the message Bye Bob, not Bye Alice even though we changed the name to Alice after deferring the call.

Using multiple defer statements

Go allows you to have multiple defer statements in a function. Go will execute them in the reverse order of their appearance:

func outer() {
    defer inner1()
    defer inner2()
    // function body
    // ...
}Code language: Go (go)

Inside the outer function, we have two defer statements that schedule the inner1() and inner2() functions to execute after the outer function completes. Go will execute the inner2() function first and then the inner1() function.

Behind the scenes, Go pushes deferred function calls onto a stack. When the surrounding function returns, Go executes the deferred calls in the last-in, first-out (LIFO) order. For example:

package main

import "fmt"

func closeDbConnection() {
    fmt.Println("Close the database connection")
}
func closeFile() {
    fmt.Println("Close the file")
}

func writeDataToFile() {
    defer closeDbConnection()
    defer closeFile()
    //
    fmt.Println("Open a database connection")
    fmt.Println("Retrieving data from the database")
    fmt.Println("Write data to the file")
    fmt.Println("Open a file")
}

func main() {
    writeDataToFile()
}Code language: Go (go)

Output:

Open a database connection
Open a file
Retrieving data from the database
Write data to the file
Close the file
Close the database connectionCode language: Go (go)

How it works.

Step 1. Define the closeDbConnection function:

func closeDbConnection() {
    fmt.Println("Close the database connection")
}Code language: Go (go)

Step 2. Define the closeFile() function:

func closeFile() {
    fmt.Println("Close the file")
}Code language: Go (go)

Step 3. Define the writeDataToFile function:

func writeDataToFile() {
    defer closeDbConnection()
    defer closeFile()
    //
    fmt.Println("Open a database connection")
    fmt.Println("Retrieving data from the database")
    fmt.Println("Write data to the file")
    fmt.Println("Open a file")
}Code language: Go (go)

How writeDataToFile works.

First, defer the closeDbConnection and closeFile functions to ensure that the file and database connection are closed after the function returns:

defer closeDbConnection()
defer closeFile()Code language: Go (go)

Go will execute the closeFile function before the closeDbConnection function.

Step 4. Call the writeDataToFile() function inside the main() function:

func main() {
    writeDataToFile()
}Code language: Go (go)

Go defer use cases

  • Resource cleanup: you can use the defer keyword to always clean up properly resources such as closing files and database connection.
  • Logging: you can use the defer keyword to ensure that a certain log statement executes when a function returns.
  • Locking: you can use the defer keyword to ensure a lock is unlocked when a function exits.

Measure function execution time

The following example uses the defer keyword defer a function call to measure the execution time of a function:

package main

import (
    "fmt"
    "time"
)

func fn() {
    fmt.Println("Called the fn function")
    for i := 0; i < 1_000_000_000; i++ {
    }
}

func main() {
    start := time.Now()
    defer func() {
        elapsed := time.Since(start)
        fmt.Printf("It took %d ms\n", elapsed.Milliseconds())
    }()

    // measure execution time of the fn
    fn()
}Code language: Go (go)

Output:

Called the fn function
It took 411 msCode language: Go (go)

How it works.

First, define fn function that performs 1 billion loop iterations:

func fn() {
   fmt.Println("Called the fn function")
   for i := 0; i < 1_000_000_000; i++ {
   }
}Code language: Go (go)

Second, declare the start variable and initialize its values to the current local time:

start := time.Now()Code language: Go (go)

The Now() function from the time module returns the current local time.

Third, defer a call to an anonymous function inside the main function:

defer func() {
    elapsed := time.Since(start)
    fmt.Printf("It took %d ms\n", elapsed.Milliseconds())
}()Code language: Go (go)

The function literal gets the duration using the Since() function of the time module and displays the elapsed time in milliseconds.

Finally, call the fn() function:

fn()Code language: Go (go)

After executing the fn() function, the main() function returns. In turn, Go will execute the defer anonymous function call to print out the duration.

defer scope

The scope of the defer statement is function. It means that the deferred calls must wait until the function exits.

Sometimes, you may not want to use the defer keyword. For example:

package main

import (
    "fmt"
    "io"
    "os"
)

func main() {
    filenames := []string{"1.txt", "2.txt", "3.txt"}

    for _, filename := range filenames {
        file, err := os.Open(filename)
        if err != nil {
            fmt.Println(err)
            continue
        }
        // read file
        data, err := io.ReadAll(file)
        if err != nil {
            fmt.Println(err)
            continue
        }
        fmt.Println(string(data))
        // Close the file
        file.Close()
    }
}Code language: JavaScript (javascript)

In this example, the program iterates over filenames, reads each file, and displays the contents on the screen.

After each iteration, we call the Close() method to close the file without using the defer keyword.

If we used the defer keyword, then the file would close when the function exits, not after each iteration, potentially causing an error of running out of file descriptors.

Defer Gotcha

A defer statement executes before the return is done. See the following example:

package main

import "fmt"

func getX() (x int) {
    defer func() {
        x = 10
    }()
    x = 5
    return
}

func main() {
    fmt.Println(getX())
}
Code language: Go (go)

Output:

10

How it works.

First, define getX() function that returns an integer. It has a named return value.

Second, call the defer function that sets the return value to 10:

defer func() {
   x = 10
}()

Third, set the return value to 5 and return:

x = 5
returnCode language: JavaScript (javascript)

Finally, call the getX() function in the main function and print out the return value.

The return value in this case is 10, not 5 because the defer statement executes a function literal that sets x to 10 before the return is done.

Summary

  • Use the defer keyword to schedule a function call at the surrounding function exit.
  • A function may have multiple defer statements. Go executes the deferred function calls in the reverse order of their appearance.
  • Use the defer keyword for resource cleanup, logging, and locking.
  • The scope of the defer statement is its surrounding function.
Was this tutorial helpful?