Skip to main content

Defer, Panic, and Recover

Created: December 17, 2025 4 min read

Defer, panic, and recover are Go’s mechanisms for cleanup, error signaling, and exception-like handling. Understanding these is crucial for robust Go programs. For more context, see Go Installation Guide, Go Ecosystem Overview, Go Best Practices.

Defer Statement

Basic Defer

package main

import "fmt"

func main() {
    defer fmt.Println("Deferred 1")
    defer fmt.Println("Deferred 2")
    defer fmt.Println("Deferred 3")

    fmt.Println("Main")
}

// Output:
// Main
// Deferred 3
// Deferred 2
// Deferred 1

Defer with Functions

package main

import "fmt"

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

func process() {
    defer cleanup()
    fmt.Println("Processing...")
}

func main() {
    process()
}

// Output:
// Processing...
// Cleaning up...

Resource Cleanup

File Handling

package main

import (
    "fmt"
    "os"
)

func readFile(filename string) {
    file, err := os.Open(filename)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    defer file.Close()

    // Read file...
    fmt.Println("File opened and will be closed automatically")
}

func main() {
    readFile("data.txt")
}

Database Connections

package main

import "fmt"

type Database struct {
    connected bool
}

func (db *Database) Connect() error {
    db.connected = true
    fmt.Println("Connected to database")
    return nil
}

func (db *Database) Close() error {
    db.connected = false
    fmt.Println("Disconnected from database")
    return nil
}

func queryDatabase() {
    db := &Database{}
    if err := db.Connect(); err != nil {
        fmt.Println("Error:", err)
        return
    }
    defer db.Close()

    // Query database...
    fmt.Println("Querying database")
}

func main() {
    queryDatabase()
}

Defer with Arguments

Argument Evaluation

package main

import "fmt"

func main() {
    x := 10
    defer fmt.Println("x is", x)  // x evaluated now

    x = 20
    fmt.Println("x is", x)
}

// Output:
// x is 20
// x is 10

Defer with Named Return Values

package main

import "fmt"

func divide(a, b int) (result int, err error) {
    defer func() {
        fmt.Printf("Dividing %d by %d, result: %d, error: %v\n", a, b, result, err)
    }()

    if b == 0 {
        err = fmt.Errorf("division by zero")
        return
    }

    result = a / b
    return
}

func main() {
    divide(10, 2)
    divide(10, 0)
}

Panic

Triggering Panic

package main

import "fmt"

func main() {
    fmt.Println("Start")
    panic("Something went wrong!")
    fmt.Println("End")  // Never reached
}

// Output:
// Start
// panic: Something went wrong!

Panic with Conditions

package main

import "fmt"

func divide(a, b int) int {
    if b == 0 {
        panic("division by zero")
    }
    return a / b
}

func main() {
    fmt.Println(divide(10, 2))  // 5
    fmt.Println(divide(10, 0))  // Panic!
}

Recover

Basic Recover

package main

import "fmt"

func safeDivide(a, b int) (result int) {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered from panic:", r)
            result = 0
        }
    }()

    if b == 0 {
        panic("division by zero")
    }
    return a / b
}

func main() {
    fmt.Println(safeDivide(10, 2))  // 5
    fmt.Println(safeDivide(10, 0))  // 0 (recovered)
}

Recover with Error Handling

package main

import (
    "fmt"
    "log"
)

func processData(data []int) {
    defer func() {
        if r := recover(); r != nil {
            log.Printf("Panic recovered: %v", r)
        }
    }()

    for i := 0; i < 10; i++ {
        fmt.Println(data[i])  // Panic if index out of bounds
    }
}

func main() {
    data := []int{1, 2, 3}
    processData(data)
    fmt.Println("Program continues")
}

Defer Patterns

Multiple Defers

package main

import "fmt"

func transaction() {
    fmt.Println("Begin transaction")

    defer func() {
        fmt.Println("Rollback")
    }()

    defer func() {
        fmt.Println("Commit")
    }()

    fmt.Println("Execute queries")
}

func main() {
    transaction()
}

// Output:
// Begin transaction
// Execute queries
// Commit
// Rollback

Defer with Closures

package main

import "fmt"

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

// Output:
// Deferred: 2
// Deferred: 1
// Deferred: 0

Best Practices

✅ Good: Use Defer for Cleanup

// DO: Use defer for resource cleanup
func processFile(filename string) error {
    file, err := os.Open(filename)
    if err != nil {
        return err
    }
    defer file.Close()

    // Process file
    return nil
}

❌ Bad: Manual Cleanup

// DON'T: Forget cleanup in error paths
func processFile(filename string) error {
    file, err := os.Open(filename)
    if err != nil {
        return err
    }

    // Process file
    file.Close()  // Might not execute if error occurs
    return nil
}

✅ Good: Recover Only When Necessary

// DO: Use recover for exceptional cases
defer func() {
    if r := recover(); r != nil {
        log.Printf("Panic: %v", r)
    }
}()

❌ Bad: Overuse Panic/Recover

// DON'T: Use panic for normal error handling
if err != nil {
    panic(err)  // Use error returns instead
}

✅ Good: Document Panic Behavior

// DO: Document when functions panic
// Divide panics if divisor is zero
func Divide(a, b int) int {
    if b == 0 {
        panic("division by zero")
    }
    return a / b
}

Summary

Defer, panic, and recover provide:

  1. Automatic cleanup with defer
  2. Exception-like behavior with panic/recover
  3. Guaranteed execution of deferred functions
  4. LIFO ordering for multiple defers
  5. Resource safety patterns

These features enable robust error handling and resource management in Go.

Resources

Comments

Share this article

Scan to read on mobile