skip to content

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%

answer

  1. binding, not a copy
  2. when it runs, not when it was made
  3. reassignment afterwards is visible
  4. const stops rebinding, not mutation
  5. parameters give you a fresh binding

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.

solid answer

~40 s

A JavaScript function keeps a reference to the environment it was created in, not a snapshot of the values in it. Identifiers inside the function body are resolved when the function **runs**, by walking that environment chain, so if the outer variable is reassigned in between, the closure sees the new value. `let msg = "first"; const show = () => msg; msg = "second";` — `show()` returns `"second"`. The same applies when the variable is rebound to a whole new object: the closure follows the binding, not the old object. The only "snapshot" you ever get is one you create yourself, either by copying the value into a fresh binding (a parameter, or a `const` local declared at capture time) or by calling the outer function again, which creates a brand-new environment.

code

javascript · 9 lines
javascript
let msg = "first";
const show = () => msg;
msg = "second";
console.log(show()); // "second"

let arr = [1, 2];
const readLen = () => arr.length;
arr = [1, 2, 3, 4];
console.log(readLen()); // 4 — follows the binding, not the old array

go deeper

for a junior

Be ready to state plainly that a closure looks the variable up when it runs, so a value changed afterwards is the value it sees, and to demonstrate it in three lines of code.

for a middle

Explain the mechanism: the function stores a reference to the environment it was created in, and free identifiers resolve by walking that chain at call time. Cover const rebinding versus object mutation.

for a senior

Show how this explains real bugs — handlers reading stale-looking state, factories that unexpectedly share — and name the deliberate snapshot techniques you reach for when a callback must not see later changes.

for a principal

Own the API-design angle: decide when a factory should hand out live views of shared state and when it should hand out frozen copies, and make that contract explicit so callers are never guessing which one they got.

## What "capture" means here A closure is a function together with the environment in which it was created. The word *capture* misleads people because in some languages a lambda can copy values into itself. JavaScript never does that. When a function object is created, it stores a reference to the *environment record* it was created in — the spec calls this the function's `[[Environment]]` internal slot. When the function later runs, every free identifier in its body (any name not declared inside the function) is resolved by walking that chain of environment records outward until the name is found, and only then is a value read. The consequence follows directly: the read happens at **call time**, not at creation time. Whatever the binding holds at the moment the closure executes is what the closure sees. ## Reassignment after creation is visible ``` let msg = "first"; const show = () => msg; msg = "second"; show(); // "second" ``` Nothing about `show` changed; the binding `msg` changed, and `show` was always pointing at the binding. This also works in the other direction — a closure can be created *before* the variable ever holds a useful value, and will still work once it does, as long as it is called afterwards. That is why a function defined at the top of a module can legitimately read a value assigned lower down at init time. ## Rebinding a whole object is visible too People sometimes accept "primitives are live" but assume the closure holds on to the object it saw: ``` let arr = [1, 2]; const readLen = () => arr.length; arr = [1, 2, 3, 4]; readLen(); // 4, not 2 ``` The closure did not remember the first array. It remembers the name `arr` in a particular environment, and that name now denotes a different array. ## What `const` does and does not do `const` prevents *rebinding* of that particular name, so a closure over a `const` really does always see the same value. It does not deep-freeze anything: if the value is an object, mutating its properties is still fully visible through the closure, because nothing about the binding changed. `const config = {...}` plus `config.level = "debug"` is seen by every closure over `config`. ## Parameters are the built-in way to snapshot Each call to a function creates a fresh environment record with fresh bindings for its parameters, initialised from the argument values. For a primitive argument that is effectively a copy: later reassignment of the *caller's* variable is invisible to the closure, because the closure sees the parameter binding, not the caller's. ``` let n = 1; function hold(x) { return () => x; } const f = hold(n); n = 99; f(); // 1 ``` For an object argument the parameter holds a reference to the same object, so property mutations are still shared — the snapshot is of the reference, not of the object's contents. The same trick works without a helper: declare a local `const copy = value;` inside the enclosing function and close over `copy` instead. That is the honest way to freeze a value at capture time, and it is worth saying out loud in an interview, because it shows you understand that the language gives you liveness by default and a snapshot only on request. ## Why interviewers care Almost every closure bug in real code is a mismatch between the two mental models. Someone builds a handler or a callback expecting it to have "remembered" the value that was in scope when it was created, and instead it reads whatever is there when it fires. Conversely, someone assumes a factory hands out independent state, then discovers all the products read a single shared outer variable. Both are the same fact viewed from opposite sides: closures see bindings, and bindings live in an environment that outlives the call that created it. ## How to answer out loud Say "by binding, not by value", give the two-line reassignment example, then add the two refinements that separate a solid answer from a memorised one: `const` stops rebinding but not mutation, and a parameter (or a local `const` initialised at capture time) is how you deliberately take a snapshot.

  • If the outer variable is declared with const, is the closure then capturing a value?
    No — it is still capturing the binding; `const` simply forbids reassigning that binding, so the value happens never to change. If the value is an object, mutating its properties is fully visible through the closure, because the binding still points at the same object. Immutability of the name is not immutability of the data.
  • How would you deliberately freeze a value at the moment a closure is created?
    Copy it into a fresh binding that nothing else reassigns: pass it as an argument to a wrapper function, or declare `const snapshot = value;` in the enclosing scope and close over `snapshot`. For primitives that is a real snapshot. For objects you have only snapshotted the reference, so you additionally need a copy (spread, `structuredClone`) if the contents must not drift.
  • Can a closure read a variable that is assigned only after the closure is created?
    Yes, provided the closure runs after the assignment. The identifier is resolved at call time, so a function defined early can read a value wired up later during initialisation. If it runs before the assignment, a `let`/`const` binding is still uninitialised and throws a ReferenceError, while a `var` binding reads `undefined`.

saying these in an interview costs you the question

  • Says the closure copies the value when it is defined
  • Claims const makes the captured object immutable
  • Thinks the closure keeps the old object after the variable is reassigned
  • Believes reassignments made after creation are invisible to the closure
  • Says JavaScript has capture-by-value and capture-by-reference modes

context