deferinloop: Detect deferred cleanup in Go loopsSkip to content
Blog
Resources and lifecycle

deferinloop

Checks cleanup defers whose lifetime extends across loop iterations.

Tiercore
Kindhazard

What it detects

A defer inside a loop runs when the surrounding function returns, not when the iteration ends. Put cleanup-sensitive work in a helper function so files, locks, timers, and similar resources are released after each iteration.

The full list of precision boundaries, with the fixtures that pin each one, is in the deferinloop design notes.

Checks

Check Tier Kind What it detects
cleanup-lifetime
core hazard Reports cleanup defers whose lifetime extends across loop iterations.

Why this is flagged

Deferring cleanup until the whole function returns lets resources accumulate across iterations. A large or long-running loop can then exhaust file handles, hold locks too long, or keep timers and connections alive unnecessarily.

A resource stored in a collection that outlives the loop is not reported, because the collection may keep it on purpose.

Further reading: Effective Go: Defer.

How to fix it

Move the body of one iteration into a small helper function and defer cleanup there. The helper returns at the end of each iteration, so its resources are released before the next iteration starts.

Examples

Flagged code

func readAll(names []string) error {
for _, name := range names {
file, err := os.Open(name)
if err != nil {
return err
}
// gohawk: deferred cleanup runs after the loop instead of after this iteration
defer file.Close()
}
return nil
}

Accepted code

func readAllSafely(names []string) error {
for _, name := range names {
if err := readOne(name); err != nil {
return err
}
}
return nil
}
func readOne(name string) error {
file, err := os.Open(name)
if err != nil {
return err
}
defer file.Close()
return nil
}