This guide explains closures in Go for readers who already know functions and variable scope. A closure is a function value that captures variables from outside its body; it is related to but not the same as an anonymous function. The page covers capture and mutation, returning closures for small state machines, common uses (including sort.Search), goroutine pitfalls, and golang closure performance without treating closures as inherently slow.
Tested with Go 1.24 on Linux.
Quick answer: closure vs anonymous function
A closure is a function that uses variables from an enclosing scope. Those variables stay alive as long as the function value does, so the closure can read and update captured state across calls. An anonymous function is simply a function literal without a name; it becomes a closure when it captures outer variables. A nameless function that only uses its parameters and globals is anonymous but not a meaningful “closure” in the usual sense.
How closures capture variables
The inner function closes over msg: each returned function value keeps its own msg.
package main
import "fmt"
func greeter(prefix string) func(string) string {
return func(name string) string {
return prefix + ", " + name
}
}
func main() {
hi := greeter("Hello")
fmt.Println(hi("Ada"))
fmt.Println(hi("Bob"))
}You should see two lines like Hello, Ada and Hello, Bob. The captured prefix stays fixed for the lifetime of hi.
Captured variables can be mutated, and that state persists between calls of the same function value:
package main
import "fmt"
func counter() func() int {
n := 0
return func() int {
n++
return n
}
}
func main() {
next := counter()
fmt.Println(next(), next(), next())
other := counter()
fmt.Println(other())
}You should see 1, 2, 3 on the first line (space-separated in one Println) then 1—other is a fresh closure with its own n.
Returning a closure from a function
Functions are first-class values: an outer function can return an inner function that still uses the outer parameters or locals. That pattern implements small factories, counters, and configuration helpers without exposing mutable state globally.
package main
import "fmt"
func multiplier(factor int) func(int) int {
return func(x int) int {
return x * factor
}
}
func main() {
times3 := multiplier(3)
fmt.Println(times3(4), times3(10))
}You should see 12 30.
Common closure use cases
| Use | Role of the closure |
|---|---|
| Counter or accumulator | Private captured int or slice |
sort.Search callback |
Reads outer slice while binary searching |
| HTTP middleware | Wraps http.Handler with shared config |
defer paired setup |
Captures resources for teardown |
| Small callbacks | Keeps behavior local without new top-level names |
sort.Search takes a predicate closure that sees the sorted slice in the outer scope:
package main
import (
"fmt"
"sort"
)
func main() {
input := 14
arrayInt := []int{5, 4, 1, 12, 8, 2, 7, 25, 11, 13, 14, 9, 10, 6, 3}
sort.Ints(arrayInt)
i := sort.Search(len(arrayInt), func(j int) bool {
return arrayInt[j] >= input
})
if i < len(arrayInt) && arrayInt[i] == input {
fmt.Println("found at index", i)
} else {
fmt.Println("not found")
}
}You should see found at index 13 for this fixed input.
Closures and goroutines
If a closure starts a goroutine and captures a loop variable, every goroutine must see the value for that iteration, not the variable slot that the loop reuses. Copy the value into a local or pass it into the goroutine function literal as a parameter.
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var wg sync.WaitGroup
for _, id := range []int{1, 2, 3} {
id := id // line copy for the closure below
wg.Add(1)
go func() {
defer wg.Done()
time.Sleep(10 * time.Millisecond)
fmt.Println("job", id)
}()
}
wg.Wait()
}You should see three lines with job 1, job 2, and job 3 in some order. Without id := id, older Go versions could print the same id three times; since Go 1.22 per-iteration loop variables reduce this class of bug, but explicit copies remain a clear style for goroutine-heavy code.
If multiple goroutines mutate the same captured variable without synchronization, you have a data race—use a mutex, channel, or avoid shared mutable capture.
Closure performance (golang closure performance)
Closures are not automatically slow. The cost is whatever work the closure does plus allocator behavior: if the closure outlives its stack frame or is stored in an interface or map, captured variables may escape to the heap, which can mean more allocations and GC pressure in hot paths.
The compiler decides escapes; you can inspect decisions with go build -gcflags=-m=2 on a small package. Prefer measuring with pprof before rewriting readable closures into heavier abstractions. In tight loops, an ordinary function or method that does not close over changing heap data can sometimes allocate less—but that is a profiling outcome, not a rule against closures.
When to use or avoid closures
Use closures for small, localized behavior, private state between calls of the same function value, middleware-style wrapping, and callbacks that need outer data.
Avoid them when the logic is large, reused in many packages, or when captured mutable shared state would be clearer as struct fields with documented synchronization—or when profiling shows this specific closure in a hot path and a simpler call shape reduces allocations.
Mistakes to avoid
Treating “anonymous” and “closure” as identical—anonymous describes syntax; closure describes capture.
Starting goroutines that close over a loop variable without a per-iteration copy on older codebases (and confusing readers even on Go 1.22+).
Letting several goroutines mutate the same captured variable without a mutex or channel.
Hiding important logic inside deeply nested closures where a named function or type would read better.
Assuming every closure is slow—verify with benchmarks and escape analysis.
Go closure cheat sheet
| Situation | Recommendation |
|---|---|
| Function uses outer variable | Closure |
| Function literal has no name | Anonymous function |
| Literal captures nothing meaningful | Closure term rarely needed |
| Private state across calls | Returned closure or struct |
| Reused across packages | Named function or type |
| Stateful behavior with methods | Struct + methods may be clearer |
| Goroutine inside loop | Copy loop value or pass as parameter |
| Shared mutable capture | Synchronize or do not share |
| Hot path cost unknown | Profile; check escape analysis |
| Readable local callback | Closure is idiomatic |
Summary
A golang closure is a function value that captures variables from an enclosing lexical scope so they survive with the function. Anonymous functions are one way to write such values; capture is what makes them closures in practice. Returning closures gives you lightweight factories and counters; sort.Search and HTTP middleware are everyday examples. For goroutines, make capture of loop data explicit when multiple concurrent closures run. For golang closure performance, trust profiling and escape analysis over folklore—closures are fine unless evidence shows otherwise.

