DEV Community

Cover image for The Secret Life of Go: The 'defer' Statement
Aaron Rose
Aaron Rose

Posted on

The Secret Life of Go: The 'defer' Statement

How to fix resource leaks and master Go's "Proximity Rule"


Chapter 20: The Stacked Deck

The fan on Ethan's desktop PC was spinning loudly. He was staring at a terminal that was spewing error messages like a broken fire hydrant.

panic: too many open files
panic: too many open files

"I don't get it," he muttered, hitting Ctrl+C to kill the server. "I'm closing everything. I checked three times."

Eleanor walked by, carrying a tray of tea. "Show me the ExportData function."

Ethan pulled up the code. It was a long function, maybe 50 lines, that opened a file, queried a database, wrote to a CSV, and then uploaded it to S3.

func ExportData(id string) error {
    f, err := os.Open("data.csv")
    if err != nil {
        return err
    }

    db, err := sql.Open("postgres", "...")
    if err != nil {
        return err // <--- The Leak
    }

    // ... 40 lines of logic ...

    db.Close()
    f.Close() // <--- The Cleanup
    return nil
}

Enter fullscreen mode Exit fullscreen mode

"I close the file right there at the bottom," Ethan pointed.

"And what happens if the database connection fails?" Eleanor asked gently.

Ethan looked at the second if err != nil block. "It returns the error."

"And does it close the file before returning?"

Ethan froze. "No. It returns immediately. The file stays open."

"Exactly. And if you have an error in the middle of your 40 lines of logic? The file stays open. If the function panics? The file stays open. You have created a leak."

The Proximity Rule

"In other languages," Eleanor explained, "you might wrap this in a try...finally block. You put the cleanup way down at the bottom, far away from where you opened the resource. You have to remember to scroll down and check it."

"In Go, we prefer Proximity."

She took the keyboard and moved the cleanup code.

func ExportData(id string) error {
    f, err := os.Open("data.csv")
    if err != nil {
        return err
    }
    defer f.Close() // Scheduled immediately!

    db, err := sql.Open("postgres", "...")
    if err != nil {
        // f.Close() happens automatically here
        return err 
    }
    defer db.Close() // Scheduled immediately!

    // ... 40 lines of logic ...

    return nil
    // db.Close() happens automatically here
    // f.Close() happens automatically here
}

Enter fullscreen mode Exit fullscreen mode

"The defer keyword pushes a function call onto a stack," Eleanor said. "It says: 'I don't care how this function ends—return, error, or panic—run this code right before you leave.'"

"So I put the cleanup right next to the creation?"

"Always. Open the door, then immediately tell the door to shut itself when you leave. You will never forget again. It works for files, database connections, and especially Mutex locks."

mu.Lock()
defer mu.Unlock() // The lock is released no matter what happens

Enter fullscreen mode Exit fullscreen mode

The Stack (LIFO)

"Wait," Ethan said, looking at the code. "I have two defers now. Which one runs first?"

"It is a stack," Eleanor replied. "Last In, First Out."

She sketched it on a notepad:

  1. Open File (Push f.Close)
  2. Open DB (Push db.Close)
  3. Function Ends ->
  4. Pop DB Close (Runs 1st)
  5. Pop File Close (Runs 2nd)

"This is critical," she noted. "Imagine you are writing a buffered writer. You need to Flush the buffer before you Close the file. Since you create the File first and the Writer second, the Writer closes first. The dependency order is handled naturally."

The Argument Trap

"One warning," Eleanor added, holding up a finger. "The function call is scheduled for later, but the arguments are evaluated now."

"What does that mean?"

"Look at this."

func TrackTime() {
    start := time.Now()
    defer fmt.Println("Time elapsed:", time.Since(start))

    time.Sleep(2 * time.Second)
}

Enter fullscreen mode Exit fullscreen mode

Ethan ran the code.
Time elapsed: 0s

"Zero?" Ethan asked. "But it slept for two seconds."

"Because time.Since(start) was calculated when you wrote the defer line," Eleanor explained. "At that exact moment, start was now, so the difference was zero."

"How do I fix it?"

"Use an anonymous function to delay the execution."

defer func() {
    fmt.Println("Time elapsed:", time.Since(start))
}()

Enter fullscreen mode Exit fullscreen mode

Ethan ran it again.
Time elapsed: 2s

"Now the calculation happens inside the function, at the very end."

The Safety Net

Ethan looked at his ExportData function. It looked safer. Robust.

"I used to think defer was just syntax sugar," he said.

"It is a safety net," Eleanor corrected. "We are humans. We forget things. We get distracted. defer allows you to clean up as you go, so you never leave a mess for your future self."


Key Concepts

The defer Statement:
Schedules a function call to be run immediately before the surrounding function returns.

  • Use Case: Closing files, releasing locks (mu.Unlock), closing database connections.

Execution Order (LIFO):
Deferred calls are executed in Last In, First Out order.

  • The last thing you deferred is the first thing to run.
  • Example: If you defer closing a file and then defer closing a database, the database closes first.

Panic Safety:
Deferred functions run even if the program panics. This makes them essential for recovering from crashes or ensuring resources are released during a failure.

Argument Evaluation:

  • Arguments to the deferred function are evaluated immediately (when the defer line is hit).
  • The Function Body is executed later (when the function returns).
  • Tip: If you need to calculate something at the end (like timing execution), wrap it in an anonymous func() { ... }.

Next chapter: The Panic and Recover. Ethan learns that sometimes, the only way to save a program is to let it crash (and then catch it).


Aaron Rose is a software engineer and technology writer at tech-reader.blog and the author of Think Like a Genius.

Top comments (13)

Collapse
 
pascal_cescato_692b7a8a20 profile image
Pascal CESCATO

Your articles are always fascinating. I genuinely prefer learning through engaging narratives like yours rather than through dry tutorials — so thank you for that, Aaron.

Just a simple question, more out of curiosity than anything else: have you ever considered grouping your Go, JavaScript, and other pieces into structured series instead of keeping them standalone? I feel like they already form a kind of narrative universe, and seeing them organized as series could make them even more powerful for readers following along.

Collapse
 
aaron_rose_0787cc8b4775a0 profile image
Aaron Rose

i'll check that out! thank you Pascal 🙏❤

Collapse
 
bhavin-allinonetools profile image
Bhavin Sheth

This is a great explanation of defer 👍
I really like the proximity rule framing — opening a resource and immediately deferring its cleanup makes the code much safer and easier to reason about. The LIFO example and the argument-evaluation gotcha are especially helpful for people who think they already “know” defer but still get bitten by it.

Collapse
 
aaron_rose_0787cc8b4775a0 profile image
Aaron Rose

❤🙏

Collapse
 
harsh2644 profile image
Harsh

“Absolutely loved this deep dive into Go’s defer statement! Who knew that a tiny little keyword could have such a big impact on keeping your code clean and bug-free? The article really demystified the subtle behaviors of defer—especially the order of execution—which I’ve sometimes underestimated in my own projects. The examples were super practical and made me appreciate how defer can save us from common mistakes like forgetting to close files or unlock mutexes. After reading this, I’m definitely going to be more mindful about where and how I use defer. A must-read for anyone who wants to write Go code that’s both elegant and robust!”

Collapse
 
aaron_rose_0787cc8b4775a0 profile image
Aaron Rose

🙏❤️

Collapse
 
cyber8080 profile image
Cyber Safety Zone

Great read! 🙌 I love how this article breaks down Go’s defer in a clear and practical way — it’s one of those features that seems simple but can be surprisingly powerful (and tricky) in real code. Thanks for the helpful insights!

Collapse
 
aaron_rose_0787cc8b4775a0 profile image
Aaron Rose

🙏❤✨

Collapse
 
benjamin_nguyen_8ca6ff360 profile image
Benjamin Nguyen

nice! Go looks much easier than javascript?

Collapse
 
aaron_rose_0787cc8b4775a0 profile image
Aaron Rose

Hi Benjamin, yep Go is definitely simpler by design, which makes it feel much less cluttered! While JavaScript is still the king of the browser, Go shines on the backend where you need raw speed and reliability. Cheers! ✨❤

Collapse
 
benjamin_nguyen_8ca6ff360 profile image
Benjamin Nguyen

Nice! I need to take some time to learn GO. I don't use node.js for the backend anymore.

Collapse
 
blueberry_adii profile image
Aditya Prasad

That was a great way of explaining defer in Go, learnt new things for sure, thanks mate!!

Collapse
 
aaron_rose_0787cc8b4775a0 profile image
Aaron Rose

🙏❤️🌹