Closures & Defer
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.
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!")
}
- 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:
Click Run to execute your code
- 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
Click Run to execute your code
The Defer Statement
The defer keyword schedules a function call to run after the
surrounding function
returns:
Click Run to execute your code
- 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
Click Run to execute your code
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.
Enjoying these tutorials?