DEV Community

Cover image for Understanding defer in Go: Best Practices, Common Pitfalls, and Why It Matters
Abhishek Kumar
Abhishek Kumar

Posted on

Understanding defer in Go: Best Practices, Common Pitfalls, and Why It Matters

Understanding Go's defer Statement: A Complete Guide

Have you ever written a Go code? Of course you have - that's why this post is recommended to you! And if your answer is yes, then you might have come across the keyword defer. So let's understand this in detail so that we can write better Go code (after all, as gophers our aim is to write code that works and not fall for anything like other language folks do, haha just kidding).


But What is defer?

So if you look for the official definition it goes like: "defer schedules a function call to run right after the surrounding function returns". Ok but what does this actually mean? In simple terms, this means that any function call preceded by defer is added to a stack of deferred calls and will be executed just before the parent function (where the defer is declared) exits.

The deferred calls are executed in Last-In, First-Out (LIFO) order.

So How Does defer Work?

Let's take a code example which will help us understand this better and clear up what is the LIFO execution that we talked about earlier. So below is a simple Go code with some addition and some print statements. I think this is a pretty simple yet effective example to understand defer without buzzwords.

package main

import "fmt"

func main() {
    a := 54
    b := 34
    c := a + b
    fmt.Println(c)  // Prints the sum immediately
    defer fmt.Printf("from defer: %v\n", a + b)  
    defer fmt.Println("hello world from defer")
    fmt.Printf("Hello, World!\n")
}
Enter fullscreen mode Exit fullscreen mode

Any guesses what will be the output? Let's look at the output first:

88
Hello, World!
hello world from defer
from defer: 88
Enter fullscreen mode Exit fullscreen mode

Why and How This Happens

Let's analyze the execution flow of Go's defer statement step by step.

Step-by-Step Execution

Immediate Execution:

  • Computes c = a + b88
  • Prints: 88

Deferring Function Calls:

defer fmt.Printf("from defer: %v\n", a + b)
defer fmt.Println("hello world from defer")
Enter fullscreen mode Exit fullscreen mode

Arguments are evaluated immediately:

  • a + b88
  • "hello world from defer" is ready
  • Functions themselves are pushed onto a stack for later execution

Regular Execution Continues:

  • Prints: Hello, World!

Deferred Functions Execute in Reverse (LIFO):

  1. First, prints the last deferred call:
   hello world from defer
Enter fullscreen mode Exit fullscreen mode
  1. Then, prints the first deferred call:
   from defer: 88
Enter fullscreen mode Exit fullscreen mode

Complete Output

88
Hello, World!
hello world from defer
from defer: 88
Enter fullscreen mode Exit fullscreen mode

Key Takeaways

  • Arguments to deferred functions are evaluated when defer is encountered
  • Deferred functions execute in Last-In-First-Out (LIFO) order
  • Deferred functions run just before the surrounding function returns

More Common Use Cases: File Handling

package main

import (
    "fmt"
    "os"
)

func main() {
    file, err := os.Open("example.txt")
    if err != nil {
        fmt.Println("Error opening file:", err)
        return
    }

    // Ensure the file gets closed when the function exits
    defer file.Close()

    fmt.Println("File opened successfully.")
}
Enter fullscreen mode Exit fullscreen mode

👉 Here, file.Close() is guaranteed to run at the end of main(), even if the function returns early due to an error. This helps a lot in resource cleanup and making sure the file is closed after usage.

Ok, So We've Understood defer, Now Let's Do Something Fun

One interesting trick with defer is reversing a string without using loops or extra arrays. Since deferred functions execute in Last-In, First-Out (LIFO) order, we can take advantage of this to print characters in reverse.

package main

import "fmt"

func main() {
    word := "golang"
    fmt.Print("Reversed word: ")

    for i := 0; i < len(word); i++ {
        ch := word[i] // capture the character immediately
        defer fmt.Printf("%c", ch)
    }
}
Enter fullscreen mode Exit fullscreen mode

How It Works

  1. Each iteration of the loop defers printing a character
  2. Deferred calls are stacked, so they execute in reverse order when main() ends
  3. The result is the string printed backwards:
Reversed word: gnalog
Enter fullscreen mode Exit fullscreen mode

Conclusion

I hope this post has helped you understand defer in an easy and fun way!

Happy coding in Go! 🐹

Top comments (1)

Collapse
 
abhishek_writes profile image
Abhishek Kumar

Feel free to add something, you feel is missing here.