Defer Close Pattern
Fast recall
Quick revision
Core answer
Whether you understand Defer Close Pattern beyond memorized syntax.
Giving a definition without explaining a practical trade-off.
Connect the concept to a small production example and state its complexity or failure mode.
Interview-ready guide
Interview answer
The pattern, executed:
func readFile(name string) error {
f, err := os.Open(name)
if err != nil {
return err // error path — no leak (nothing acquired yet)
}
defer f.Close() // EVERY exit from here: file closes
// ... work with f ...
return nil
}The discipline: defer f.Close() comes AFTER the error check (you can't close a nil file) and BEFORE any other return path — that's the invariant: every Open followed immediately by its deferred Close.
The subtlety interviewers probe: Close returns an error too (buffered writes may fail on close — deferred close silently discards it). For read-only files, the discard is safe. For writes, the pattern that surfaces close errors:
func writeFile(name string) error {
f, err := os.Create(name)
if err != nil { return err }
defer f.Close()
if _, err := f.WriteString("data"); err != nil {
return err // f.Close runs via defer
}
return f.Close() // explicit close to catch the error
}The pattern with a named return + defer is the general form:
func writeFile(name string) (err error) {
f, err := os.Create(name)
if err != nil { return err }
defer func() {
if cerr := f.Close(); cerr != nil && err == nil {
err = cerr // surface the close error
}
}()
// ... work ...
return
}The general resource rule: whatever has a Cleanup/Close/Unlock, the defer goes at acquisition — files, network connections, mutexes, response bodies (the HTTP client's resp.Body defer is the same discipline).
Defer Close Pattern
Direct Answer
defer f.Close() immediately after acquiring the file — it runs on every exit path (normal, early return, panic). The rule: acquisition and cleanup at the same scope, defer connecting them.
Deep Dive & Architecture
The Close's own failure mode: for a file being WRITTEN, Close flushes OS buffers — if the disk is full or permissions changed, Close fails and data is lost. The read-only case never fails meaningfully. The write case's contract: the data isn't durable until Close succeeds — that's why the write path's Close error is part of the operation's result, not cleanup noise.
The scope-coupling design: Go's defer binds cleanup to the scope of acquisition — as long as Open and Close are in the same function, the defer guarantees pairing. The pattern breaks when Open escapes the scope (returning an open file — now the CALLER owns the close). The explicit-ownership pattern for that: document it (io.Closer interfaces, 'the caller must Close').
The resource-leak math that motivates the discipline: Go's GC eventually finalizes files, but not before running out of file descriptors. The default ulimit (often 1024) means ~1024 concurrent unclosed files hang the process. In a long-running server, one unclosed file per request = a few hours to a crash.
Common Mistakes & Pitfalls
Giving a definition without explaining a practical trade-off.
Check your understanding
Answer these without looking back at the article.
- When would you use Defer Close Pattern in a real project?
- How would you compare Defer Close Pattern with an alternative?
- What is a common mistake when using Defer Close Pattern?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.