Web Analytics

Closures & Defer

Intermediate ~30 min read

Closures and defer are powerful features in Go. Closures allow functions to capture and use variables from their surrounding scope, while defer ensures cleanup code runs at the right time. Together, they enable elegant patterns for resource management and functional programming.

Closures and Defer in Go - Side by Side Comparison

Figure: Closures vs Defer - Two powerful Go features for advanced function patterns and resource management

Anonymous Functions

Anonymous functions are functions without a name. They can be defined inline and assigned to variables:

func main() {
    // Anonymous function assigned to variable
    greet := func(name string) {
        fmt.Printf("Hello, %s!\n", name)
    }
    
    greet("Alice")  // Hello, Alice!
    
    // Immediately invoked function
    func(msg string) {
        fmt.Println(msg)
    }("This runs immediately!")
}
Anonymous Function Uses:
  • One-time operations
  • Callbacks
  • Goroutines (concurrent functions)
  • Creating closures

Closures

A closure is a function that references variables from outside its body. The function "closes over" these variables:

Output
Click Run to execute your code
How Closures Work:
  • The inner function can access variables from the outer function
  • Variables are "captured" by reference, not by value
  • Each closure maintains its own copy of captured variables
  • Captured variables persist as long as the closure exists

Practical Example: Counter

Output
Click Run to execute your code
Pro Tip: Closures are perfect for creating factory functions that generate customized functions with specific behavior!

The Defer Statement

The defer keyword schedules a function call to run after the surrounding function returns:

Output
Click Run to execute your code
Defer Rules:
  • Deferred calls are executed in LIFO order (last in, first out)
  • Arguments are evaluated immediately, but the call is delayed
  • Runs even if the function panics
  • Perfect for cleanup operations

Common Use: Resource Cleanup

Output
Click Run to execute your code
Best Practice: Always defer cleanup operations (closing files, unlocking mutexes, etc.) immediately after acquiring the resource. This ensures cleanup happens even if an error occurs!

Defer Execution Order

func main() {
    defer fmt.Println("First defer")
    defer fmt.Println("Second defer")
    defer fmt.Println("Third defer")
    fmt.Println("Main function")
}

// Output:
// Main function
// Third defer
// Second defer
// First defer

Defer with Arguments

When you defer a function, its arguments are evaluated immediately:

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

// Output:
// Current: 20
// Deferred: 10  (uses the value when defer was called)

Using Closures with Defer

To capture the final value, use a closure:

func main() {
    x := 10
    
    // Closure captures x by reference
    defer func() {
        fmt.Println("Deferred:", x)
    }()
    
    x = 20
    fmt.Println("Current:", x)
}

// Output:
// Current: 20
// Deferred: 20  (uses the final value)

Practical Patterns

1. Timing Function Execution

func timeTrack(start time.Time, name string) {
    elapsed := time.Since(start)
    fmt.Printf("%s took %s\n", name, elapsed)
}

func slowFunction() {
    defer timeTrack(time.Now(), "slowFunction")
    // Function body
    time.Sleep(2 * time.Second)
}

2. Mutex Locking

var mu sync.Mutex
var balance int

func deposit(amount int) {
    mu.Lock()
    defer mu.Unlock()  // Ensures unlock even if panic occurs
    
    balance += amount
}

3. Database Transactions

func updateUser(db *sql.DB, user User) error {
    tx, err := db.Begin()
    if err != nil {
        return err
    }
    defer tx.Rollback()  // Rollback if commit not called
    
    // Perform updates...
    
    return tx.Commit()  // Commit overrides rollback
}

Common Mistakes

1. Defer in a loop

// โŒ Wrong - defers accumulate, files stay open
func processFiles(files []string) {
    for _, filename := range files {
        f, _ := os.Open(filename)
        defer f.Close()  // Won't close until function ends!
        // Process file...
    }
}

// โœ… Correct - use a separate function
func processFiles(files []string) {
    for _, filename := range files {
        processFile(filename)
    }
}

func processFile(filename string) {
    f, _ := os.Open(filename)
    defer f.Close()  // Closes when processFile returns
    // Process file...
}

2. Ignoring defer return values

// โŒ Wrong - error from Close is ignored
func readFile(filename string) error {
    f, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer f.Close()  // Error ignored!
    
    // Read file...
    return nil
}

// โœ… Correct - check error in named return
func readFile(filename string) (err error) {
    f, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer func() {
        if cerr := f.Close(); cerr != nil && err == nil {
            err = cerr
        }
    }()
    
    // Read file...
    return nil
}

3. Closure variable capture in loops

// โŒ Wrong - all closures reference same variable
for i := 0; i < 3; i++ {
    defer func() {
        fmt.Println(i)  // All print 3!
    }()
}

// โœ… Correct - pass as parameter
for i := 0; i < 3; i++ {
    defer func(n int) {
        fmt.Println(n)  // Prints 2, 1, 0
    }(i)
}

Exercise: Function Timer

Task: Create a reusable function timer using defer and closures.

Requirements:

  • Create a timer() function that returns a cleanup function
  • Use defer to call the cleanup function
  • Measure and print execution time
  • Test with a slow function (use time.Sleep)
Show Solution
package main

import (
    "fmt"
    "time"
)

// timer returns a function that prints elapsed time
func timer(name string) func() {
    start := time.Now()
    return func() {
        elapsed := time.Since(start)
        fmt.Printf("%s took %v\n", name, elapsed)
    }
}

// Example slow function
func slowOperation() {
    defer timer("slowOperation")()  // Note the ()()
    
    fmt.Println("Starting slow operation...")
    time.Sleep(2 * time.Second)
    fmt.Println("Operation complete!")
}

// Alternative: using named return
func fastOperation() {
    stop := timer("fastOperation")
    defer stop()
    
    fmt.Println("Fast operation")
    time.Sleep(500 * time.Millisecond)
}

func main() {
    slowOperation()
    fmt.Println()
    fastOperation()
}

// Bonus: Generic timer with closure
func measure(fn func()) time.Duration {
    start := time.Now()
    fn()
    return time.Since(start)
}

func exampleUsage() {
    duration := measure(func() {
        time.Sleep(1 * time.Second)
    })
    fmt.Printf("Took: %v\n", duration)
}

Summary

  • Anonymous functions are functions without names
  • Closures capture variables from their surrounding scope
  • Closures maintain state across multiple calls
  • defer schedules function calls to run after the function returns
  • Deferred calls execute in LIFO order (last in, first out)
  • defer is perfect for cleanup (closing files, unlocking mutexes)
  • Arguments to defer are evaluated immediately
  • Use closures with defer to capture final values

What's Next?

Now that you understand closures and defer, you're ready to learn about Panic & Recover. In the next lesson, you'll discover Go's approach to handling exceptional situations and recovering from runtime errors.