Skip to main content

Overview

defer is used to ensure that a function call is performed later in a program’s execution, usually for purposes of cleanup. defer is often used where ensure and finally would be used in other languages.

Basic Defer Usage

A deferred function call is executed when the surrounding function returns, regardless of how it returns:
func main() {
    // Immediately after getting a file object with
    // createFile, we defer the closing of that file
    // with closeFile. This will be executed at the end
    // of the enclosing function (main), after
    // writeFile has finished.
    path := filepath.Join(os.TempDir(), "defer.txt")
    f := createFile(path)
    defer closeFile(f)
    writeFile(f)
}
Deferred functions are executed in LIFO (Last In, First Out) order when the surrounding function returns.

Why Use Defer

Defer provides several benefits:
  • Keeps cleanup close to acquisition - Resource cleanup code is right next to the code that acquires the resource
  • Guaranteed execution - Deferred functions run even if the function panics
  • Cleaner code - Eliminates the need for complex cleanup logic scattered throughout the function

Common Use Cases

File Handling

func createFile(p string) *os.File {
    fmt.Println("creating")
    f, err := os.Create(p)
    if err != nil {
        panic(err)
    }
    return f
}

func writeFile(f *os.File) {
    fmt.Println("writing")
    fmt.Fprintln(f, "data")
}

func closeFile(f *os.File) {
    fmt.Println("closing")
    err := f.Close()
    // It's important to check for errors when closing a
    // file, even in a deferred function
    if err != nil {
        panic(err)
    }
}

Mutex Unlocking

mu.Lock()
defer mu.Unlock()

// Critical section
// Mutex will be unlocked when function returns

Database Connections

rows, err := db.Query("SELECT * FROM users")
if err != nil {
    return err
}
defer rows.Close()

// Process rows
// Connection will be closed when function returns

Complete Example

package main

import (
	"fmt"
	"os"
	"path/filepath"
)

// Suppose we wanted to create a file, write to it,
// and then close when we're done. Here's how we could
// do that with defer.
func main() {
	path := filepath.Join(os.TempDir(), "defer.txt")
	f := createFile(path)
	defer closeFile(f)
	writeFile(f)
}

func createFile(p string) *os.File {
	fmt.Println("creating")
	f, err := os.Create(p)
	if err != nil {
		panic(err)
	}
	return f
}

func writeFile(f *os.File) {
	fmt.Println("writing")
	fmt.Fprintln(f, "data")
}

func closeFile(f *os.File) {
	fmt.Println("closing")
	err := f.Close()
	if err != nil {
		panic(err)
	}
}

Output

Running the program confirms that the file is closed after being written:
$ go run defer.go
creating
writing
closing

Multiple Defers

You can have multiple defer statements in a single function. They execute in reverse order (LIFO):
func main() {
    defer fmt.Println("First")
    defer fmt.Println("Second")
    defer fmt.Println("Third")
    fmt.Println("Main function")
}

// Output:
// Main function
// Third
// Second
// First
Deferred functions are executed in Last In, First Out (LIFO) order, which makes sense for cleanup operations that need to happen in reverse order of acquisition.

Defer with Anonymous Functions

You can defer anonymous functions, which is useful for more complex cleanup logic:
func processFile(filename string) error {
    f, err := os.Open(filename)
    if err != nil {
        return err
    }
    
    defer func() {
        if err := f.Close(); err != nil {
            log.Printf("Error closing file: %v", err)
        }
    }()
    
    // Process file
    return nil
}

Important Considerations

The arguments to a deferred function are evaluated when the defer statement is executed, not when the deferred function actually runs:
func main() {
    i := 0
    defer fmt.Println(i) // Prints 0, not 5
    i = 5
}
Deferred functions can read and modify named return values:
func example() (result int) {
    defer func() {
        result++
    }()
    return 5
} // Returns 6
While defer has minimal overhead, in extremely performance-critical code, direct cleanup calls might be slightly faster. However, the readability and safety benefits usually outweigh this.

Best Practices

Defer immediately after acquisition

Place defer statements right after acquiring a resource to ensure cleanup happens.

Check errors in deferred functions

Always check for errors in deferred cleanup functions, especially when closing files.

Use for cleanup only

Reserve defer for cleanup operations. Don’t use it for general code organization.

Be aware of loops

Deferred functions in loops accumulate until the function returns, which can cause memory issues.

Defer in Loops - A Common Pitfall

Be careful when using defer inside loops. Deferred functions accumulate and only execute when the surrounding function returns, not at the end of each iteration.
// Bad: Resource leak in loop
for _, file := range files {
    f, _ := os.Open(file)
    defer f.Close() // Won't close until function ends
    // Process file
}

// Good: Use a separate function
for _, file := range files {
    processFile(file)
}

func processFile(filename string) error {
    f, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer f.Close() // Closes after each iteration
    // Process file
    return nil
}

Panic

Learn how defer interacts with panic situations.

Recover

Use defer with recover to handle panics gracefully.

Build docs developers (and LLMs) love