Closures
You already know functions are values in Go — you can put one in a variable and pass it around. A closure takes that one step further: it's a function that remembers variables from the scope where it was created.
A function that captures its surroundings
When you write a function literal that mentions a variable from the enclosing scope, the function "closes over" that variable — hence closure:
package main
import "fmt"
func main() {
message := "hi"
greet := func() { fmt.Println(message) }
message = "hello"
greet() // hello
}
greet didn't copy message when it was defined; it captured the variable
itself. Changing message afterward changes what greet prints. Closures
capture by reference, not by value.
Captured variables outlive the outer call
Here's the surprising part. The captured variable stays alive as long as the closure does — even after the function that declared it has returned:
package main
import "fmt"
func makeCounter() func() int {
count := 0
return func() int {
count++
return count
}
}
func main() {
next := makeCounter()
fmt.Println(next()) // 1
fmt.Println(next()) // 2
fmt.Println(next()) // 3
}
makeCounter returns and its stack frame is gone — yet count lives on,
privately held by the returned closure. This "factory" pattern is the classic
use of closures: bundle some hidden state with a function that operates on it.
Each closure has its own state
Every call to the factory makes a fresh variable, so the closures it returns are independent:
a := makeCounter()
b := makeCounter()
fmt.Println(a(), a(), b()) // 1 2 1
a and b count separately because each got its own count.
Why capture by reference?
Capturing the variable (not a snapshot) is what makes the counter work — every
call has to see and update the same count. It's also the one thing to stay
alert about: if several closures capture the same variable, they all see its
latest value.
That used to bite people in for loops, where every closure captured the one
shared loop variable. As of Go 1.22, each iteration gets its own copy of the
loop variable, so a closure created inside the loop captures that iteration's
value — the intuitive behavior, and one less footgun.
Practice
1. Adder Factory
BeginnerImplement
makeAdder, a closure factory. It takes a starting value and returns a function that keeps a running total: each call adds its argument to the total and returns the new total.func makeAdder(start int) func(int) intFor example:
add := makeAdder(10) add(5) // 15 add(3) // 18The running total must be captured by the returned closure, so it survives between calls. A second, independent
makeAddermust keep its own separate total.package main import "fmt" // makeAdder returns a closure that keeps a running total starting at start. // Each call adds its argument to the total and returns the new total. func makeAdder(start int) func(int) int { // TODO: implement return func(n int) int { return 0 } } func main() { add := makeAdder(10) fmt.Println(add(5)) // 15 fmt.Println(add(3)) // 18 }Sign in to write and run your solution in the browser.
Sign in for hints and the solution.