Closures & Function Values
Prerequisites: defer, panic & recover
In Go, functions are values: you can store them in variables, pass them
as arguments, and return them from other functions. A function type is
written with its signature, such as func(int) int.
A closure is a function literal that refers to variables from the scope around it. The closure keeps those variables alive and shares them — it does not copy them.
Syntax
var op func(int, int) int // zero value of a function type is nil
op = func(a, b int) int { return a + b }
type Transform func(string) string // a named function type
func apply(nums []int, f func(int) int) []int { /* ... */ }
Example
package main
import (
"fmt"
"strings"
)
func apply(nums []int, f func(int) int) []int {
out := make([]int, 0, len(nums))
for _, n := range nums {
out = append(out, f(n))
}
return out
}
// counter returns a closure that remembers count between calls.
func counter() func() int {
count := 0
return func() int {
count++
return count
}
}
func multiplier(factor int) func(int) int {
return func(n int) int { return n * factor }
}
func main() {
double := multiplier(2)
fmt.Println(apply([]int{1, 2, 3}, double))
fmt.Println(apply([]int{1, 2, 3}, func(n int) int { return n * n }))
next := counter()
fmt.Println(next(), next(), next())
other := counter() // a separate closure with its own count
fmt.Println(other())
shout := strings.ToUpper // named functions are values too
fmt.Println(shout("go"))
var op func(int, int) int
fmt.Println(op == nil)
}
Expected output
[2 4 6]
[1 4 9]
1 2 3
1
GO
true
Since Go 1.22, each iteration of a for loop gets a fresh copy of the
loop variable, so closures created inside a loop capture the value from their
own iteration. (This applies when your go.mod says go 1.22 or later;
older code often has a v := v line to work around the previous behaviour.)
Common mistakes
- Calling a
nilfunction value — it panics. Checkf != nilfirst if the function is optional. - Forgetting that closures share variables: if two closures capture the same variable, a change made through one is visible through the other.
- Comparing two function values with
==. Functions can only be compared tonil.
Practice
Write makeAccumulator() func(int) int that returns a closure adding each
argument to a running total and returning the new total. Call it with 10
and then 5, printing 10 and 15.
Show solution
package main
import "fmt"
func makeAccumulator() func(int) int {
total := 0
return func(n int) int {
total += n
return total
}
}
func main() {
add := makeAccumulator()
fmt.Println(add(10)) // 10
fmt.Println(add(5)) // 15
}