Defer Statement in Golang

Defer Statement in Golang

The defer statement in Go is used to delay the execution of a function until the surrounding function returns. Deferred functions are executed in Last In, First Out (LIFO) order, which makes them perfect for cleanup operations like closing files, releasing resources, or unlocking mutexes.

Basic Defer Usage

basic-defer.go
package main

import "fmt"

func main() {
    fmt.Println("Start")
    
    defer fmt.Println("This will be executed last")
    
    fmt.Println("Middle")
    
    defer fmt.Println("This will be executed second")
    
    fmt.Println("End")
}

Output:

Start
Middle
End
This will be executed second
This will be executed last

Defer with Function Calls

Defer works with any function call, not just print statements.

defer-functions.go
package main

import (
    "fmt"
    "time"
)

func doSomething() {
    fmt.Println("Doing something...")
    time.Sleep(100 * time.Millisecond)
}

func cleanup() {
    fmt.Println("Cleaning up...")
}

func main() {
    defer cleanup() // This will run when main exits
    
    fmt.Println("Starting main")
    doSomething()
    fmt.Println("Main finished")
}

Resource Management

Defer is commonly used for resource cleanup:

file-handling.go
package main

import (
    "fmt"
    "os"
)

func main() {
    file, err := os.Open("example.txt")
    if err != nil {
        fmt.Println("Error opening file:", err)
        return
    }
    
    // Defer file close - this will run when main exits
    defer file.Close()
    
    // Read from file...
    buffer := make([]byte, 100)
    n, err := file.Read(buffer)
    if err != nil {
        fmt.Println("Error reading file:", err)
        return
    }
    
    fmt.Printf("Read %d bytes: %s\n", n, buffer[:n])
    
    // File will be automatically closed here due to defer
}

Multiple Defers - LIFO Order

Multiple defer statements execute in reverse order:

multiple-defers.go
package main

import "fmt"

func main() {
    fmt.Println("Main started")
    
    defer fmt.Println("Defer 1")
    defer fmt.Println("Defer 2")
    defer fmt.Println("Defer 3")
    
    fmt.Println("Main finished")
}

Output:

Main started
Main finished
Defer 3
Defer 2
Defer 1

Defer with Parameters

Deferred functions capture the current value of variables at the time defer is executed, not when the function is called:

defer-parameters.go
package main

import "fmt"

func main() {
    i := 0
    defer fmt.Println("Deferred value:", i) // Captures i=0
    
    i = 10
    fmt.Println("Current value:", i) // Prints 10
}

Output:

Current value: 10
Deferred value: 0

For functions with parameters, the parameters are evaluated when defer is executed:

defer-evaluation.go
package main

import "fmt"

func printValue(x int) {
    fmt.Println("Value:", x)
}

func main() {
    x := 1
    
    defer printValue(x) // x is evaluated here as 1
    
    x = 2
    fmt.Println("x is now:", x)
}

Defer in Loops

Be careful with defer in loops - each iteration creates a new deferred function:

defer-loop.go
package main

import "fmt"

func main() {
    for i := 0; i < 3; i++ {
        defer func() {
            fmt.Println("Deferred:", i)
        }()
    }
    
    fmt.Println("Loop finished")
}

Output:

Loop finished
Deferred: 3
Deferred: 3
Deferred: 3

To capture the loop variable correctly, pass it as a parameter:

defer-loop-fixed.go
package main

import "fmt"

func main() {
    for i := 0; i < 3; i++ {
        defer func(n int) {
            fmt.Println("Deferred:", n)
        }(i) // Pass i as parameter
    }
    
    fmt.Println("Loop finished")
}

Output:

Loop finished
Deferred: 2
Deferred: 1
Deferred: 0

Defer with Methods

Defer works with methods too:

defer-methods.go
package main

import (
    "fmt"
    "sync"
)

type Counter struct {
    mu    sync.Mutex
    value int
}

func (c *Counter) Increment() {
    c.mu.Lock()
    defer c.mu.Unlock() // Unlock when function returns
    
    c.value++
    fmt.Println("Incremented to:", c.value)
}

func main() {
    counter := &Counter{}
    counter.Increment()
}

Panic and Recover with Defer

Defer is commonly used with panic and recover for error handling:

defer-panic.go
package main

import "fmt"

func safeDivision(a, b int) (result int, err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("division by zero: %v", r)
        }
    }()
    
    if b == 0 {
        panic("division by zero")
    }
    
    result = a / b
    return
}

func main() {
    result, err := safeDivision(10, 0)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    fmt.Println("Result:", result)
}

Defer Performance Considerations

Defer has a small performance cost. In performance-critical code, you might want to avoid defer:

defer-performance.go
package main

import (
    "fmt"
    "time"
)

func withDefer() {
    start := time.Now()
    for i := 0; i < 1000000; i++ {
        func() {
            defer func() {}() // Empty defer
        }()
    }
    fmt.Printf("With defer: %v\n", time.Since(start))
}

func withoutDefer() {
    start := time.Now()
    for i := 0; i < 1000000; i++ {
        func() {
            // No defer
        }()
    }
    fmt.Printf("Without defer: %v\n", time.Since(start))
}

func main() {
    withDefer()
    withoutDefer()
}

Common Patterns

File Operations

file-pattern.go
package main

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

func processFile(filename string) error {
    file, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer file.Close() // Ensure file is closed
    
    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        line := scanner.Text()
        fmt.Println(line)
    }
    
    return scanner.Err()
}

Database Transactions

db-pattern.go
package main

import (
    "database/sql"
    "fmt"
)

func updateUser(db *sql.DB, userID int, newName string) error {
    tx, err := db.Begin()
    if err != nil {
        return err
    }
    defer tx.Rollback() // Rollback if not committed
    
    _, err = tx.Exec("UPDATE users SET name = ? WHERE id = ?", newName, userID)
    if err != nil {
        return err
    }
    
    // If we reach here, commit the transaction
    return tx.Commit()
}

Mutex Locking

mutex-pattern.go
package main

import (
    "fmt"
    "sync"
)

type SafeCounter struct {
    mu    sync.Mutex
    value int
}

func (c *SafeCounter) Increment() {
    c.mu.Lock()
    defer c.mu.Unlock() // Ensure unlock happens
    
    c.value++
    fmt.Println("Value:", c.value)
}

HTTP Response Cleanup

http-pattern.go
package main

import (
    "fmt"
    "net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
    // Simulate some processing
    fmt.Fprintln(w, "Hello, World!")
    
    // In a real handler, you might defer cleanup operations
    defer func() {
        // Cleanup code here
        fmt.Println("Request cleanup")
    }()
}

Best Practices

  1. Use defer for cleanup - Always use defer for resource cleanup
  2. Keep defer simple - Deferred functions should be simple and not complex
  3. Be aware of evaluation timing - Remember that defer captures values at defer time
  4. Use defer in loops carefully - Pass loop variables as parameters if needed
  5. Defer order matters - Multiple defers execute in reverse order
  6. Don’t ignore defer performance - Avoid defer in hot loops if performance is critical
  7. Use defer with recover - Combine defer with recover for error handling

External Resources:

Related Tutorials:

Last updated on