thecodex.expert · The Codex Family of Knowledge
Go

Closures

Closures capture variables by reference, not by value — which used to make loop variables a classic source of bugs, until Go 1.22 changed the rule.

Go 1.26 Fixed in Go 1.22 Last verified:
Canonical Definition

A closure is a function literal that references a variable declared outside its own body. Go closures capture that variable by reference, not by value — meaning if the outer variable changes after the closure is created, the closure sees the new value, not a frozen snapshot from when it was defined.

A closure capturing shared state

A common use is a function that returns another function, which keeps its own private state across calls via the captured variable:

Gocounter.go
func makeCounter() func() int {
    count := 0
    return func() int {   // this closure captures 'count' by reference
        count++
        return count
    }
}

c := makeCounter()
fmt.Println(c())   // 1
fmt.Println(c())   // 2 — count persisted between calls
fmt.Println(c())   // 3

The classic loop-variable gotcha (fixed in Go 1.22)

Before Go 1.22 (released February 2024), a for loop reused a single variable across every iteration, so closures created inside the loop — especially goroutines launched per iteration — all captured the SAME variable, which usually held its final value by the time they ran. Go 1.22 changed the language so each iteration gets its own fresh copy of the loop variable, eliminating an entire category of bugs and the x := x workaround that was previously idiomatic.

Goloop_capture.go
for i := 0; i < 3; i++ {
    go func() {
        fmt.Println(i)   // Go 1.22+: prints 0, 1, 2 (each iteration, own i)
    }()                    // before Go 1.22: often printed 3, 3, 3
}

Why this matters for code written before 2024

Any Go codebase or tutorial predating Go 1.22 may still contain the manual workaround, explicitly shadowing the loop variable inside the loop body (i := i) before using it in a closure. That workaround is now unnecessary but harmless — it's a useful signal for reading older Go code, and safe to remove once a project's minimum Go version is 1.22 or later.

Sources

1
The Go Authors. Go 1.22 Release Notes, "Changes to the language" section, go.dev/doc/go1.22 — covers the per-iteration loop variable change.