16. Closures
An anonymous function can use variables from the function that surrounds it. When it does, it closes over them — it keeps them alive, and keeps seeing their current values. That's a closure, and it's how you build functions with memory.
A function that remembers
package main
import "fmt"
func main() {
count := 0
increment := func() int {
count++
return count
}
fmt.Println(increment())
fmt.Println(increment())
fmt.Println(increment())
fmt.Println("count is now", count)
}
increment doesn't take count as a parameter and doesn't declare its own.
It reaches out to main's count and modifies it. Every call sees the
change the last one made.
The key point: the closure captures the variable, not a copy of its
value. There is exactly one count here, and both main and increment
are looking at it.
Returning a closure
This is where it becomes a tool. A function can build a closure and hand it back — and the captured variable survives even though the outer function has returned:
package main
import "fmt"
func counter() func() int {
n := 0
return func() int {
n++
return n
}
}
func main() {
a := counter()
b := counter()
fmt.Println(a(), a(), a())
fmt.Println(b())
}
a prints 1, 2, 3 and b prints 1. Each call to counter() creates a
fresh n, and the returned closure keeps its own alive. n isn't on the
stack any more — Go noticed it outlives counter and moved it to the heap
for you. No manual memory management, no dangling pointer.
Configuring behaviour
The most common practical use: a function that builds a specialised function.
package main
import "fmt"
func multiplier(factor int) func(int) int {
return func(n int) int {
return n * factor
}
}
func main() {
double := multiplier(2)
triple := multiplier(3)
fmt.Println(double(10), triple(10))
fmt.Println(double(triple(2)))
}
double and triple are the same code with different captured state. You
just wrote a tiny factory.
The same trick makes middleware, retry wrappers and rate limiters read nicely — each is a function that wraps and returns another function.
Closures with defer
Remember from the control-flow module that a deferred call's arguments are evaluated immediately. Defer a closure instead and it reads the variable at the moment it runs, not at the moment you deferred it:
package main
import "fmt"
func main() {
total := 0
defer func() {
fmt.Println("final total:", total)
}()
total += 10
total += 5
fmt.Println("working... total is", total)
}
The deferred closure prints 15, because it reads total when it runs at
the end of main. This is the standard way to log a summary, record a
duration, or inspect a result on the way out of a function.
The classic trap: capturing a loop variable
Here is the bug every Go programmer writes once. Suppose you build a slice of closures inside a loop:
funcs := []func(){}
for _, name := range []string{"a", "b", "c"} {
funcs = append(funcs, func() {
fmt.Println(name) // which name?
})
}
for _, f := range funcs {
f()
}
In Go 1.21 and earlier, name was a single variable reused by every
iteration, so all three closures shared it and all three printed c — the
last value. It caused so many bugs that the language changed: since Go
1.22, each iteration gets its own name, and this prints a b c.
The old fix, which you'll still see everywhere in existing code, is to make the copy explicit — either shadow the variable or pass it as a parameter:
package main
import "fmt"
func main() {
funcs := []func(){}
for _, name := range []string{"a", "b", "c"} {
name := name // explicit per-iteration copy
funcs = append(funcs, func() {
fmt.Println(name)
})
}
for _, f := range funcs {
f()
}
}
name := name looks absurd until you know why it's there. On modern Go it's
redundant; in older code it's load-bearing. Either way, the underlying rule
is the one to remember: a closure captures the variable, not the value.
Whenever a closure outlives the loop that made it — and goroutines are the
big case — ask yourself which variable it's actually holding.
Your turn
Write accumulator, which returns a function that adds its argument to a
running total and returns the new total. The program should print exactly:
10
30
33
package main
import "fmt"
// write accumulator here — it returns a func(int) int
func main() {
add := accumulator()
fmt.Println(add(10))
fmt.Println(add(20))
fmt.Println(add(3))
}
package main
import "fmt"
func accumulator() func(int) int {
total := 0
return func(n int) int {
total += n
return total
}
}
func main() {
add := accumulator()
fmt.Println(add(10))
fmt.Println(add(20))
fmt.Println(add(3))
}
That completes functions. Next module: the containers you'll put your data in — slices, arrays and maps.