Go's `defer`: Guaranteed Cleanup
Go's `defer` statement guarantees cleanup by running a function call just before the parent function returns. It's perfect for closing files or unlocking mutexes right where you acquire them. The footgun: multiple defers run in last-in, first-out order.
WHY IT EXISTS: In functions with multiple return points or potential panics, it's easy to forget to release a resource like a file handle or a mutex. The defer statement was created to solve this by guaranteeing that cleanup code runs no matter how a function exits, preventing resource leaks.
THE MENTAL MODEL: Think of defer as setting a "do this on my way out" reminder. When you open a door (acquire a resource), you immediately place a sticky note on the inside of the door that says "close this." You can then do whatever you need to in the room, and when you leave (the function returns), you'll see the note and close the door.
HOW IT WORKS: When Go encounters a defer statement, it pushes the function call onto a special stack. The arguments to the deferred call are evaluated immediately at this time. When the surrounding function is about to return, Go executes all calls on this stack in Last-In, First-Out (LIFO) order. This LIFO behavior is crucial: if you open a file and then lock a mutex, you'll want to unlock the mutex first, then close the file. defer handles this naturally.
WHEN TO USE IT: Use defer for any action that must be paired with an initial action to ensure proper cleanup. This is most common for closing I/O resources (files, network connections), closing database connections, and unlocking mutexes (sync.Mutex). It makes code cleaner by co-locating the setup and teardown logic.
WHEN NOT TO USE IT: Avoid using defer inside a loop that runs many times. The deferred calls only execute when the entire function returns, not at the end of each loop iteration. This can cause a slow memory leak as the stack of deferred calls grows. In such cases, perform cleanup manually at the end of each iteration. Also, deferred functions do not run if the program is terminated via os.Exit.
ONE CANONICAL EXAMPLE: A function that copies a file. It opens both the source and destination files. Using defer, we can ensure both files are closed correctly, even if an error occurs during the copy process. func copyFile(dstName, srcName string) (written int64, err error) { src, err := os.Open(srcName) if err != nil { return } defer src.Close()
dst, err := os.Create(dstName) if err != nil { return } defer dst.Close()
return io.Copy(dst, src) } Here, dst.Close() will run first, then src.Close(), following the LIFO rule.
Read the original → go.dev
Get five bites like this every day.
Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.