Introduction
The defer keyword in Go schedules a function call to be executed when the surrounding function returns. Deferred functions are essential for cleanup operations, resource management, and ensuring code executes regardless of how a function exits.
func main () {
defer fmt . Println ( "This runs last" )
fmt . Println ( "This runs first" )
}
// Output:
// This runs first
// This runs last
Basic Syntax
The defer statement delays the execution of a function until the surrounding function returns:
The deferred function executes:
After the surrounding function completes normally
When a return statement is reached
When a panic occurs (before propagating the panic)
When Defer Executes
func single () {
var count int
defer fmt . Println ( count ) // Deferred with count = 0
count ++
fmt . Println ( count ) // Prints: 1
// defer runs here when function returns
// Prints: 0 (the value when defer was called)
}
Deferred function arguments are evaluated immediately when the defer statement executes, not when the deferred function runs.
Correct Way to Capture Final Values
Use an anonymous function to capture variables by reference:
func single () {
var count int
defer func () {
fmt . Println ( count ) // Will print current value
}()
count ++
fmt . Println ( count ) // Prints: 1
// defer runs here
// Prints: 1 (current value)
}
Defer Stack (LIFO Order)
When multiple defer statements are used, they execute in Last-In-First-Out (LIFO) order:
func stacked () {
for count := 1 ; count <= 5 ; count ++ {
defer fmt . Println ( count )
}
fmt . Println ( "the stacked func returns" )
}
// Output:
// the stacked func returns
// 5
// 4
// 3
// 2
// 1
Think of deferred functions as a stack: the last one deferred is the first one executed. This is particularly useful for cleanup operations that need to happen in reverse order.
Practical Use Cases
1. Resource Cleanup
The most common use of defer is ensuring resources are cleaned up:
func processFile ( filename string ) error {
file , err := os . Open ( filename )
if err != nil {
return err
}
defer file . Close () // Ensures file is closed when function returns
// Work with file...
data := make ([] byte , 1024 )
_ , err = file . Read ( data )
return err
}
2. Unlocking Mutexes
var mu sync . Mutex
var balance int
func deposit ( amount int ) {
mu . Lock ()
defer mu . Unlock () // Ensures unlock even if panic occurs
balance += amount
if balance < 0 {
panic ( "negative balance" ) // mutex still unlocked!
}
}
3. Timing Functions
Measure function execution time elegantly:
func findTheMeaning () {
defer measure ( "findTheMeaning" )()
// Do some heavy calculation
time . Sleep ( time . Second * 2 )
}
func measure ( name string ) func () {
start := time . Now ()
fmt . Printf ( " %s starts... \n " , name )
return func () {
fmt . Printf ( " %s took %v \n " , name , time . Since ( start ))
}
}
// Output:
// findTheMeaning starts...
// findTheMeaning took 2.001s
Notice the () after measure("findTheMeaning"). The function returns a closure that gets deferred, and we call measure immediately to start the timer.
Without Defer (More Verbose)
func findTheMeaningNoDefer () {
start := time . Now ()
fmt . Printf ( " %s starts... \n " , "findTheMeaning" )
// Do some heavy calculation...
time . Sleep ( time . Second * 2 )
// Must remember to add this at every return point
fmt . Printf ( " %s took %v \n " , "findTheMeaning" , time . Since ( start ))
}
Advanced Patterns
1. Database Transactions
func updateUser ( db * sql . DB , userID int , name string ) error {
tx , err := db . Begin ()
if err != nil {
return err
}
defer func () {
if err != nil {
tx . Rollback ()
} else {
tx . Commit ()
}
}()
_ , err = tx . Exec ( "UPDATE users SET name = ? WHERE id = ?" , name , userID )
return err
}
2. Recovering from Panics
func safeProcess () ( err error ) {
defer func () {
if r := recover (); r != nil {
err = fmt . Errorf ( "panic recovered: %v " , r )
}
}()
// Code that might panic
riskyOperation ()
return nil
}
func riskyOperation () {
panic ( "something went wrong" )
}
3. Modifying Named Return Values
Deferred functions can modify named return values:
func divide ( a , b int ) ( result int , err error ) {
defer func () {
if err != nil {
result = 0 // Ensure result is 0 on error
}
}()
if b == 0 {
return 0 , errors . New ( "division by zero" )
}
return a / b , nil
}
4. Logging Function Entry/Exit
func trace ( name string ) func () {
fmt . Printf ( "Entering: %s \n " , name )
return func () {
fmt . Printf ( "Exiting: %s \n " , name )
}
}
func complexOperation () {
defer trace ( "complexOperation" )()
// Do work...
time . Sleep ( time . Second )
}
// Output:
// Entering: complexOperation
// Exiting: complexOperation
5. Context Cleanup
func worker ( ctx context . Context ) {
ctx , cancel := context . WithTimeout ( ctx , 5 * time . Second )
defer cancel () // Ensures context is cancelled
// Do work with context...
select {
case <- time . After ( 3 * time . Second ):
fmt . Println ( "work completed" )
case <- ctx . Done ():
fmt . Println ( "work cancelled" )
}
}
Common Patterns
Opening and Closing
// File operations
f , err := os . Open ( "file.txt" )
if err != nil {
return err
}
defer f . Close ()
// HTTP response bodies
resp , err := http . Get ( url )
if err != nil {
return err
}
defer resp . Body . Close ()
// Database connections
conn , err := db . Conn ( ctx )
if err != nil {
return err
}
defer conn . Close ()
Lock and Unlock
mu . Lock ()
defer mu . Unlock ()
// Critical section
// Safe even if panic occurs
Start and Stop
server . Start ()
defer server . Stop ()
timer := time . NewTimer ( 5 * time . Second )
defer timer . Stop ()
While defer is convenient, it has a small performance cost:
// Slightly faster
func withoutDefer () {
mu . Lock ()
// quick operation
mu . Unlock ()
}
// Slightly slower but safer
func withDefer () {
mu . Lock ()
defer mu . Unlock ()
// quick operation
}
The performance difference is negligible in most cases. Use defer for clarity and safety unless profiling shows it’s a bottleneck.
Order of Operations
Understanding when deferred functions run relative to other operations:
func example () ( result int ) {
defer func () {
result ++ // Runs after return value is set
}()
return 5 // Sets result to 5
}
// Returns: 6
Execution Order
Function body executes
Return values are evaluated and assigned
Deferred functions execute (LIFO order)
Function actually returns
func sequence () ( result string ) {
defer func () { result += " third" }()
defer func () { result += " second" }()
result = "first"
return result
}
// Returns: "first second third"
Common Pitfalls
1. Deferring in Loops
Be careful with defer in loops - deferred calls accumulate:
// WRONG: All file handles stay open until function returns
func processFiles ( filenames [] string ) error {
for _ , filename := range filenames {
f , err := os . Open ( filename )
if err != nil {
return err
}
defer f . Close () // Defers accumulate!
// Process file...
}
return nil
}
// CORRECT: Use a function to scope each defer
func processFiles ( filenames [] string ) error {
for _ , filename := range filenames {
if err := processFile ( filename ); err != nil {
return err
}
}
return nil
}
func processFile ( filename string ) error {
f , err := os . Open ( filename )
if err != nil {
return err
}
defer f . Close () // Closes when processFile returns
// Process file...
return nil
}
2. Argument Evaluation
Arguments are evaluated when defer executes, not when the deferred function runs:
func wrong () {
i := 0
defer fmt . Println ( i ) // i evaluated now, prints 0
i ++
}
func correct () {
i := 0
defer func () {
fmt . Println ( i ) // i evaluated when function runs, prints 1
}()
i ++
}
3. Error Shadowing
Be careful not to shadow errors in deferred functions:
// WRONG: err from defer shadows original error
func badExample () ( err error ) {
f , err := os . Open ( "file.txt" )
if err != nil {
return err
}
defer f . Close ()
_ , err = f . Read ( make ([] byte , 10 ))
// If Read fails, defer might override this error
return err
}
// CORRECT: Check errors explicitly
func goodExample () ( err error ) {
f , err := os . Open ( "file.txt" )
if err != nil {
return err
}
defer func () {
if closeErr := f . Close (); closeErr != nil && err == nil {
err = closeErr
}
}()
_ , err = f . Read ( make ([] byte , 10 ))
return err
}
Best Practices
Defer Immediately Place defer statements right after acquiring resources to prevent forgetting cleanup
Use Closures for Late Binding Wrap deferred calls in anonymous functions to capture variables by reference
Watch Loop Defers Avoid deferring in loops; use helper functions to scope each defer properly
Handle Errors Check errors from deferred cleanup operations, especially for Close() and Unlock()
Real-World Example
Here’s a comprehensive example using multiple defer patterns:
func processData ( filename string ) ( err error ) {
// Logging entry/exit
defer trace ( "processData" )()
// Timing
defer measure ( "processData" )()
// Error recovery
defer func () {
if r := recover (); r != nil {
err = fmt . Errorf ( "panic: %v " , r )
}
}()
// Resource cleanup
f , err := os . Open ( filename )
if err != nil {
return err
}
defer f . Close ()
// Transaction handling
tx , err := db . Begin ()
if err != nil {
return err
}
defer func () {
if err != nil {
tx . Rollback ()
} else {
tx . Commit ()
}
}()
// Do actual work...
return nil
}
Summary
The defer keyword is a powerful Go feature that:
Ensures cleanup code runs regardless of how a function exits
Executes in LIFO (stack) order
Evaluates arguments immediately but runs the function later
Works with panic recovery
Can modify named return values
Use defer for resource cleanup, unlocking mutexes, closing connections, and any cleanup that must happen regardless of how a function exits. It makes your code safer and more maintainable.
Next Steps
Function Basics Review fundamental function concepts and patterns
Closures Learn how deferred functions interact with closures