Why do two closures returned by the same Go function share state, while separate calls do not?
answer
- state lives in the enclosing call's locals
- one invocation, one set of variables
- call the factory twice, get two counters
- shared because declared in the same scope
- no lock comes with it
basics
~20 sEach call to the enclosing function creates its own locals, so closures returned by different calls see different variables. Closures returned by one call capture the same variables, so a write through either is visible to both.
solid answer
~50 sClosure state lives in the local variables of the function that created the closures, and a fresh set of those locals exists per call. So `counter()` returning an increment closure and a reset closure gives you two function values over one shared `n`: increment through the first and the second resets that same `n`. Call `counter()` again and you get an independent pair over a brand-new `n`. This is how Go writes small stateful function values without declaring a type — counters, sequence generators, memoisation caches, rate accounting. The state is invisible from the outside: nothing but the returned closures can name `n`, which is a genuine encapsulation win but also means it cannot be inspected, printed or reset except through a closure you deliberately return. And because it is plain shared memory, a closure over a counter is not safe to call from several goroutines; guard it or use an atomic type.
code
go · 6 linesfunc counter() (inc func() int, reset func()) {
n := 0
inc = func() int { n++; return n }
reset = func() { n = 0 }
return inc, reset
}go deeper
Be able to write the six-line counter factory from memory and predict its output, including what a second call to the factory returns.
Explain the mechanism in terms of scoping: locals are per invocation and capture is per variable, which is why sibling closures share and separate calls do not.
Volunteer the caveats without prompting — no synchronisation, no visibility in logs or a debugger — and give your rule for when the state should graduate into a struct with methods.
Treat it as an API question: captured state is invisible to callers and to future maintainers, so decide deliberately where a package exposes function values carrying hidden state versus a named type others can inspect and extend.
## The shape ```go func counter() (inc func() int, reset func()) { n := 0 inc = func() int { n++; return n } reset = func() { n = 0 } return inc, reset } ``` Two function values come back. They were written in the same scope, and both mention `n`, so both capture the same variable — the one this particular call to `counter` created. `inc()` returns 1, then 2, then 3; `reset()` puts that same `n` back to 0, and the next `inc()` returns 1. Call `counter()` a second time and the second call executes `n := 0` again, producing a different variable. The two pairs of closures do not interfere. ```go inc1, _ := counter() inc2, _ := counter() inc1() // 1 inc1() // 2 inc2() // 1 — a separate n ``` ## Why this works One fact: a function literal captures the variable, not its value. Everything else is ordinary scoping. `n` is a local of `counter`, so a new one exists for every invocation, exactly as it would if `counter` never returned a closure. What is unusual is only that the variable outlives the call — it stays valid for as long as some closure refers to it, so the closure can keep using it long after `counter` returned. So the sharing rule reads directly off the scoping rule: **closures share exactly the variables whose declarations they have in common.** Two literals in one invocation share; literals from two invocations do not; a literal nested inside another literal shares with its parent. ## What it is good for * **Counters and sequence generators** — the canonical example, and the one most often asked in an interview. * **Memoisation** — capture a map in the enclosing function and return a lookup function that fills it on miss. The map is unreachable except through the returned function. * **Accumulators and folds** — capture a running total, return an `add` closure and a `result` closure. * **Callbacks that carry configuration** — a handler closure that captures the timeout and the destination it was built with, so the caller passes only the request. * **Deliberate encapsulation** — nothing outside the returned closures can name the captured variable. There is no field to reach, no reflection path, no way for a caller to poke it. ## What it costs **Invisibility.** The state has no name a debugger user or a reviewer can look up. When something is wrong with a counter, you cannot print it; you can only call closures. This is why the same-shaped state, once it grows past one or two variables, is better held in a struct with methods: ```go type counter struct{ n int } func (c *counter) Inc() int { c.n++; return c.n } func (c *counter) Reset() { c.n = 0 } ``` That version is printable, testable field by field, extensible without changing a signature, and obvious to a newcomer reading the package. The rough rule engineers converge on: one captured variable and one or two closures, use a closure; more than that, or any need to inspect or serialise the state, use a struct. **Concurrency.** `n++` inside a closure is a plain read-modify-write of a shared variable. Two goroutines calling the same increment closure race, and the race is easy to miss precisely because there is no visible shared object at the call site. Either document the closure as single-goroutine, guard `n` with a `sync.Mutex` captured alongside it, or capture an atomic integer type instead. **Lifetime.** The captured variables live as long as the returned closures do. A closure stored in a long-lived registry keeps everything it references reachable — fine for an `int`, worth thinking about for a large map or slice. ## Common misreadings to correct * *Each closure gets its own copy of `n`.* No — they share, and demonstrating that is usually the point of the interview question. * *`n` dies when `counter` returns.* No — the variable's lifetime follows the closures that reference it. * *Two closures with identical bodies are the same value.* No — each evaluation of a function literal produces a distinct function value, and function values are not comparable to each other with `==` anyway (only against nil). * *The state is package-level.* No — it is per-call. Package-level state would give you exactly one counter for the whole program, which is the bug this pattern avoids. ## Answering it well Write the six-line factory, show that the second call is independent, name the mechanism (capture is per variable, and locals are per invocation), then volunteer the two caveats — no concurrency safety and no visibility — and say when you would reach for a struct instead. That last half is what separates a middle answer from a recital.
- When would you hold this state in a struct with methods instead of in captured variables?As soon as there is more than a variable or two, or when anyone needs to inspect, log or serialise the state. A struct gives named fields you can print in a test or a debugger, room to add behaviour without changing a returned signature, and an obvious shape for a reader. Closures win only when the state is tiny and genuinely private.
- Is a closure-based counter safe to call from several goroutines?No. The increment is an ordinary read-modify-write of one shared variable, so concurrent calls race and the race detector will flag it. Capture a `sync.Mutex` next to the counter and lock inside the closure, or capture an atomic integer type. Nothing about being a closure adds synchronisation.
- Can two function values in Go be compared with == to see whether they came from the same factory call?No. Function values in Go are only comparable to nil; comparing two of them is a compile error. If you need identity, capture an id or return a struct that carries one. Two closures over the same variable are still two distinct function values.
Each call to the factory opens a new shared notebook and hands out pens to everyone in that call. Different calls get different notebooks; pens from the same call all write in one.
saying these in an interview costs you the question
- Says each returned closure gets its own copy of the counter
- Claims the captured variable dies when the factory returns
- Assumes the state is shared across all factory calls
- Treats the closure counter as goroutine-safe by default
- Compares two function values with == to test identity