Closures
In the previous lesson a function was a recipe card. Now let's clip a note to the card—a variable the recipe works with.
A function literal can be written inside main and stored in a variable. Such a literal sees the variables of main declared above it and can change them.
A literal sees variables
The literal add adds numbers to the variable total, which is declared in main:
package main
import "fmt"
func main() {
total := 0
add := func(x int) { total += x }
add(5)
add(3)
fmt.Println(total) // 8
}Let's walk through the example line by line:
total := 0declares a variable inmain.add := func(x int) { total += x }stores the literal in the variableaddof typefunc(int). The literal is written insidemain, so it sees the variables ofmainthe same way the lines ofmainitself do.add(5)is called like an ordinary function and adds5tototalinmain.add(3)adds another3, andtotalbecomes8.fmt.Println(total)prints8.
The variable, not a copy
A function parameter is a copy of the argument, but a literal that uses total gets no copy. It works with the variable total itself, so the change lands in the same variable that main prints later.
A function that uses variables from the surrounding code is called a closure. The word has nothing to do with the "short-circuit" behavior of && and ||.
Here the note picture needs a caveat. A paper note is a copy: if you later fix the original, the note stays as it was. A closure holds not a copy but the variable itself, so the link works both ways. If you change the variable after the literal is created, a call sees the new value:
x := 1
show := func() { fmt.Println(x) }
x = 2
show() // 2Line by line:
x := 1declares a variable holding1.show := func() { fmt.Println(x) }creates the literal whilexholds1.x = 2changes the variable after the literal was created.show()readsxat the moment of the call and prints2.
Several statements in a literal
A literal's body can hold several statements, and on one line they are separated by semicolons: func() int { n++; return n }. Such a literal runs them in order:
n++incrementsn.return nreturns the already incremented value.
A closure from a function
A function can return a closure. The variable the closure uses keeps living after the function returns, as long as some variable holds the returned function, such as a below:
func counter() func() int {
n := 0
return func() int {
n++
return n
}
}
func main() {
a, b := counter(), counter()
fmt.Println(a(), a(), b()) // 1 2 1
}Let's see what happens here:
func counter() func() intdeclares a function that returns a function of typefunc() int.n := 0creates a new variablenon every call tocounter.return func() int { … }returns a new literal that increments thisnand returns it.a, b := counter(), counter()callscountertwice. Soaandbhave separate counters—two cards, each with its own note.fmt.Println(a(), a(), b())runs the calls in its argument list from left to right. The two calls toagive1and2, and the first call tobgives1again.
Press "Next step" and watch the "captured" row: a and b each have their own n, and it lives on after counter returns.
1–4package main · import "fmt"5func counter() func() int {6 n := 07 return func() int {8 n++9 return n10 }11}12 13func main() {14 a, b := counter(), counter()15 fmt.Println(a(), a(), b())16}
main calls counter twice: the first result goes to a, the second to b.
From outside, n cannot be changed: only the literal has access to it.
Closures in a loop
Interviewers like to ask this: what do closures created in the loop for i := 0; i < 3; i++ return if each returns i? The answer depends on the Go version:
- since Go 1.22, each iteration has its own variable
i, so the closures return0,1and2; - in older versions all iterations shared one variable, and after the loop every closure saw
3.
Common mistake
The literal tries to call itself by the name of the variable it is being assigned to:
fact := func(n int) int {
if n == 0 {
return 1
}
return n * fact(n-1) // ✗
}undefined: factThe name fact appears only after the whole := statement, and inside the literal it does not exist yet. The fix takes two steps:
- Declare the variable first:
var fact func(int) int. - Assign the literal separately:
fact = func(n int) int { … }.
The literal then uses the already declared variable fact.
Key points
- A closure is a function that uses variables from the surrounding code.
- A closure works with the variable itself, not a copy, so changes are visible both ways.
- Each call to
counter()creates a new variablen, so every counter keeps its own state. - Since Go 1.22, each iteration of a
forloop has its own variable, and closures from the loop see different values.
Subscribe to take the course
The course is free. Sign in with Telegram and subscribe to the channel to open every lesson.