skip to content

In Go, does a function literal capture an outer variable's value at creation, or the variable itself?

level: juniorimportance: must knowfreq 72%

answer

  1. not a photograph, a live wire
  2. reassign after creating, then call it
  3. writes inside are visible outside
  4. the variable is shared, not copied
  5. snapshot means a brand-new variable

basics

~20 s

A Go function literal captures the variable itself, not a snapshot of its value. Assignments made after the literal is created are visible when it runs, and assignments made inside it are visible to the enclosing code.

solid answer

~50 s

Go closures capture variables by reference, not by value. When a function literal mentions a local from the enclosing function, the two share one variable: if the outer code assigns to it after the literal is created, calling the literal reads the new value, and if the literal assigns to it, the enclosing function sees that write. So `msg := "before"; f := func() { fmt.Println(msg) }; msg = "after"` prints `after`. There is no effectively-final restriction as in some languages, and no capture-by-value mode. If you want a snapshot of the value at creation time, you must make a new variable — pass it as a parameter to the literal, or declare a fresh one (`m := msg`) and capture that instead. The captured variable stays valid for as long as the closure does, even after the enclosing function has returned.

code

go · 4 lines
go
msg := "before"
f := func() { fmt.Println(msg) }
msg = "after"
f() // prints: after

go deeper

for a junior

Be ready to state the rule in one sentence and demonstrate it: assign to the variable after creating the literal, call the literal, and say what prints. Know that there is no value-capture form in Go.

for a middle

Explain that the variable is shared in both directions, that only referenced variables are captured, and show the two ways to force a snapshot: a parameter, or a freshly declared local.

for a senior

Show where the rule causes production bugs — a handler reading config reassigned during setup, a shared counter incremented from several goroutines — and describe the review habit that catches capture of mutable state.

for a principal

Frame it as an API rule: decide whether your callbacks take their inputs as parameters or capture them, since captured state is invisible at the call site and turns into shared mutable state the moment a caller runs the callback concurrently.

## What a function literal is A function literal in Go is an anonymous function written inline: `func(a int) int { return a + n }`. It is an ordinary value of a function type, so it can be assigned to a variable, stored in a struct field or a slice, passed to another function, and returned. What makes it a *closure* is that its body may mention identifiers declared in the function that surrounds it. ## The rule: the variable, not the value The Go specification says function literals are closures: they may refer to variables defined in a surrounding function, and *those variables are shared between the surrounding function and the function literal*. That word — shared — is the whole answer. The literal does not copy the value it saw when the literal was evaluated. It refers to the same storage the enclosing code refers to. Two symmetric consequences follow. **Later writes from outside are visible inside.** ```go msg := "before" f := func() { fmt.Println(msg) } msg = "after" f() // prints: after ``` The literal was created while `msg` held `before`, but it never stored `before` anywhere. It reads `msg` when it runs. **Writes from inside are visible outside.** ```go total := 0 add := func(n int) { total += n } add(2) add(3) // total is now 5 ``` The closure mutates the caller's variable directly. This is legal Go and is the basis of counters, accumulators and callback-driven state. ## How this differs from other languages Engineers arriving from Java or C# often expect a rule that a captured local must be final or effectively final, and that only its value travels into the lambda. Go has no such rule: a captured variable is fully mutable from both sides. Engineers arriving from C expect the opposite hazard — a pointer to a local that dies when the frame returns. Go has no such hazard either: if a closure still refers to a variable, that variable remains valid for as long as the closure is reachable, so returning a closure over a local is safe and idiomatic. (Where the compiler chooses to place such a variable is a separate topic; from the language's point of view all you need is that the variable outlives the frame.) ## Getting a snapshot when you want one Because there is no capture-by-value form, a value snapshot has to be made explicitly, and both idioms create a *new variable* for the closure to capture: 1. **Pass it as a parameter.** `func(v string) func() { return func() { fmt.Println(v) } }(msg)` — the parameter `v` is a fresh variable initialised from `msg` at call time; later writes to `msg` cannot reach it. 2. **Declare a fresh local.** `m := msg` immediately before the literal, then capture `m`. The common shadowing idiom `v := v` is exactly this trick, reusing the name. A third option is to stop capturing at all: give the closure everything it needs through its parameters, so it becomes a pure function value. ## Which variables are captured Only the variables the literal's body actually references. A closure does not drag in the whole surrounding frame, and it does not capture names it never mentions. Nested literals capture through: an inner literal referring to a variable of the outermost function keeps that one variable shared across all three scopes. Capture is per-variable, not per-name-occurrence. Two literals created in the same scope that both mention `n` share the same `n` with each other and with the enclosing code. ## Where this bites in practice * Building a list of callbacks in a loop and expecting each one to remember the iteration it was built in — that depends on whether the loop gives each iteration its own variable. * Registering a handler that reads a configuration local, then reassigning that local during setup, and being surprised the handler uses the later value. * Assuming a closure that increments a shared counter is safe to run from several goroutines; the counter is one variable, so concurrent increments need a mutex or an atomic type. * Reading a variable in a deferred literal and expecting the value from the moment the literal was written rather than from the moment it runs. ## The mental model to keep A closure is not a photograph of the surrounding variables; it is a set of live wires to them. Everything else — the loop-variable trap, closures as counters, callbacks that keep data alive longer than expected — falls out of that single fact.

  • How do you give a Go function literal a snapshot of a variable's value at creation time?
    Create a new variable for it to capture. Either pass the value as a parameter to the literal and let the closure use the parameter, or declare a fresh local (`m := msg`) immediately before the literal and capture that. Both work because the new variable is never reassigned afterwards; there is no capture-by-value syntax in Go.
  • If a Go closure outlives the function that declared a captured local, is that variable still valid?
    Yes. Go guarantees the captured variable stays valid as long as the closure that refers to it is reachable, so returning a closure over a local is safe and common. It is still the same single variable, not a copy, so any other closure over it sees the same writes.
  • Does a Go function literal capture every local of the enclosing function?
    No — only the variables its body actually references. Locals the literal never mentions are not captured and are not kept alive by it. This matters when a closure is stored long-term: reference one small field instead of a large struct and only the small one is retained.

A closure holds the key to a mailbox, not a copy of the letter that was inside when it got the key. Whoever opens the box later sees whatever is in there now.

saying these in an interview costs you the question

  • Says the closure copies the value at creation time
  • Claims captured variables must be final or unmodified
  • Thinks assignments inside the literal are invisible outside
  • Fears a dangling reference after the enclosing function returns
  • Believes the closure captures the whole enclosing frame