skip to content

Closure Capture Semantics

Closures capture the binding itself, so later mutations are visible and two closures made in the same call share one environment. Interviewers probe with counters and factory functions to hear "by reference", not "a snapshot".

part ofJavaScriptoverview, primer and where to startread it →
on this pageshow

questions

4

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?

level: juniorimportance: must knowfreq 70%

answer

  1. one environment per call
  2. state lives in the call, not the function
  3. returned function keeps it alive
  4. two calls, two n bindings
  5. private because nothing else can name it

basics

~10 s

It 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 s

It 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 lines
javascript
function makeCounter() {
  let n = 0;
  return () => ++n;
}
const a = makeCounter();
const b = makeCounter();
a();
a();
console.log(a(), b()); // 3 1

go deeper

for a junior

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.

for a middle

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.

for a senior

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.

for a principal

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

context

open as a page

In JavaScript, when a function closes over an outer variable, does it capture that variable's value at the moment the function was created, or the variable binding itself? Show the difference with a variable that is reassigned after the function is created.

level: middleimportance: must knowfreq 74%

basics

~10 s

JavaScript closures capture the binding, not a copy of the value. The function reads the variable when it runs, so any reassignment made after the function was created is visible to the closure.

open as a page

A factory returns `{ inc, value, get }` where `value` is set to the local `count` and `get` returns `count`. After calling `inc()` twice, `get()` returns 2 but `value` is still 0. Explain why, and how you would fix it.

level: middleimportance: should knowfreq 48%

basics

~20 s

The property value was assigned the number that count held while the object literal was being built, so it is a one-time copy. get closes over the count binding and re-reads it on every call, which is why only it stays current.

open as a page

A createLogger(config) factory captures the config object it was given. Weeks later, a caller mutates config.level after several loggers were created, and every existing logger changes behaviour. Explain why this happens and how you would design the factory so captured state cannot be changed underneath it.

level: seniorimportance: should knowfreq 40%

basics

~20 s

The closure captured the parameter binding, which holds a reference to the caller's object, so property mutations are visible immediately to every logger. To stop it, read the values you need into local bindings at creation time, or copy or freeze the object before capturing it.

open as a page