Given `function makeCounter() { let n = 0; return () => ++n; }` and `const a = makeCounter(), b = makeCounter();`, what does `a(); a(); console.log(a(), b());` print, and why do the two counters not interfere?
answer
- one environment per call
- state lives in the call, not the function
- returned function keeps it alive
- two calls, two n bindings
- private because nothing else can name it
basics
~10 sIt prints 3 1. Every call to makeCounter creates a new environment with its own n, so the two returned functions close over two different bindings and count independently.
solid answer
~40 sIt logs `3 1`. Each invocation of `makeCounter` creates a fresh execution environment containing its own `n` binding; the arrow function returned from that call captures *that* environment. So `a` and `b` were produced by two separate calls and therefore increment two separate `n` bindings — `a` has been called three times, `b` once. The environment is not destroyed when `makeCounter` returns, because the returned function still references it; that is exactly what makes `n` persistent private state. Nothing outside the returned function can read or write `n`, since the only reference to that environment is held by the closure itself. Independence comes from the *call*, not from the function: two closures made in the *same* call would share one `n`.
code
javascript · 9 linesfunction makeCounter() {
let n = 0;
return () => ++n;
}
const a = makeCounter();
const b = makeCounter();
a();
a();
console.log(a(), b()); // 3 1go deeper
Know the output cold and be able to say that each call to the factory makes its own n. Practise writing the counter from memory in under thirty seconds.
Explain the environment record: a fresh one per invocation, captured by the function created inside it, kept alive by that reference after the call returns.
Use it to reason about real factories — connection pools, rate limiters, id generators — and be ready to say what accidentally moves state from per-instance to shared, and how you would catch that in review.
Frame the choice of where state lives as an API decision: per-call closure state versus module-level state changes testability, isolation between callers, and what happens when the same module is loaded twice.
## The output `a()` returns 1, then 2, then 3; `b()` returns 1. The logged line is `3 1`. ## One environment per invocation Calling a function creates a new execution context, and with it a fresh environment record holding bindings for that call's parameters and local declarations. `let n = 0` therefore creates a *new* `n` on every call to `makeCounter`. The arrow function created during that call stores a reference to that specific environment. So `makeCounter()` called twice produces two function objects pointing at two different environments, each with its own `n`. Incrementing one cannot be observed through the other. This is the single most important corollary of closure capture: independence is a property of the enclosing **call**, not of the enclosing **function**. ## Why n survives after the function returns A naive model of scope says local variables disappear when a function returns. That model is about stack frames, and it does not describe JavaScript. The environment record is an ordinary reachable object; as long as something references it — here, the returned arrow function — it stays alive and its bindings keep their values. When the last closure over it is dropped, the whole environment becomes unreachable along with it. That is why `n` behaves like an instance field: it is initialised once per construction (per call), it persists between uses, and it is genuinely private. ## Privacy is a consequence, not an extra feature There is no syntax here declaring `n` private. It is unreachable from outside simply because there is no expression anywhere in the program that names it: identifier resolution walks the *lexical* chain, and no code outside `makeCounter`'s body sits inside that chain. The only way to touch `n` is through a function that was written inside `makeCounter`. If you want a reader as well as an incrementer, you must return both from the same call. ## The contrast that makes the point Move the state out of the function and the independence vanishes: ``` let shared = 0; function makeBadCounter() { return () => ++shared; } const x = makeBadCounter(); const y = makeBadCounter(); x(); // 1 y(); // 2 — same binding ``` Here both closures resolve `shared` in the same outer environment, which was created once. Two calls to `makeBadCounter` do create two environments, but neither of them contains `shared`, so the lookup continues outward to the one binding that exists. The lesson is that what matters is *which environment holds the binding you are reading*, not how many closures exist. ## Naming the pattern This is the counter/factory idiom interviewers use to check that the candidate has the environment model rather than a memorised slogan. Related shapes you can mention: a `once` wrapper holding a `called` flag, a memoiser holding its cache, an ID generator holding a seed. All of them rely on the same two facts — per-call environment, and survival of that environment for as long as a closure references it. ## Common wrong answers Saying it prints `3 3` treats `n` as if it belonged to the function rather than the call. Saying it prints `1 1` assumes each invocation of the returned closure re-runs `let n = 0`, which it does not — the returned function's body is only `++n`. Saying `n` is garbage collected after `makeCounter` returns confuses stack frames with reachable environments. ## How to answer out loud Give the output first, then the one-sentence reason: a fresh environment per call, captured by the function created during that call. Add that this is why the pattern gives private, persistent state — and, if you want to show depth, add the contrast case where the state is declared outside the factory and the counters collide.
- Does the environment holding n disappear when makeCounter returns?No. The returned function references that environment, so it stays reachable and `n` keeps its value between calls. Only when every closure over it is dropped does the environment become unreachable. The stack frame is gone; the environment record is a separate, reachable thing.
- How would you add a way to read the count without letting callers set it?Return both functions from the same call, for example `return { inc: () => ++n, read: () => n };`. Both close over the same `n` because they were created in the same invocation. Callers get a read path and an increment path, and still no way to assign to `n` directly.
- If I move `let n = 0` outside makeCounter, what changes?All counters then share one binding, because their lookups walk past the per-call environment — which no longer declares `n` — to the single outer one. `x()` then `y()` returns 1 then 2. It also stops being private: any code in that outer scope can read or write `n`.
saying these in an interview costs you the question
- Says it prints 3 3 because n belongs to the function
- Says it prints 1 1 because n resets on each call
- Claims n is destroyed when makeCounter returns
- Thinks the two counters share state because the code is identical
- Calls n a global variable