Why is defer f.Close() inside a loop a bug, and how do you scope the cleanup correctly?
This processFiles opens and reads each file in a slice of paths. On a long list it fails with too many open files.
func processFiles(paths []string) error {
for _, p := range paths {
f, err := os.Open(p)
if err != nil {
return err
}
defer f.Close()
// ... read f ...
}
return nil
}
Find and fix the bug.
defer runs at function return, not per iteration, so every f.Close() is queued and nothing closes until the function exits — with many files you exhaust file descriptors. The same trap with defer mu.Unlock() self-deadlocks on the next iteration. Fix: move the loop body into a helper that returns each time.
- ✗Thinking
deferruns at the end of each loop iteration rather than at function return - ✗Assuming stacked
defer f.Close()is harmless because the OS reclaims descriptors at exit - ✗Overlooking that
defer mu.Unlock()in a loop self-deadlocks on the second iteration
- →How does wrapping the loop body in a closure or helper function scope the
deferper iteration? - →Why does
defer mu.Unlock()in a loop deadlock whiledefer f.Close()only leaks?
Find the bug
func processFiles(paths []string) error {
for _, p := range paths {
f, err := os.Open(p)
if err != nil {
return err
}
defer f.Close() // BUG: defers stack up until the function returns
// ... read f ...
}
return nil
}
Why it is a bug
defer is tied to the function return, not the loop iteration. Each pass queues one more deferred f.Close(), but none of them runs until processFiles returns. Over a long list the open files pile up and the process exhausts file descriptors (too many open files).
⚠️ The same trap with a lock is worse: defer mu.Unlock() in a loop does not release the mutex between iterations, so the second iteration calls mu.Lock() again on an already-held mutex → self-deadlock.
✅ The fix is to scope the defer to one iteration by moving the body into a helper:
func processFiles(paths []string) error {
for _, p := range paths {
if err := processOne(p); err != nil {
return err
}
}
return nil
}
func processOne(p string) error {
f, err := os.Open(p)
if err != nil {
return err
}
defer f.Close() // closes when processOne returns — each iteration
// ... read f ...
return nil
}