skip to content

Closures

A function bundled with the environment it was created in, still alive after the outer call returned. Closures are the single most asked JavaScript concept — private state, factories, callbacks, and the loop bug everyone has hit.

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

explore

questions

18

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, `for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0); }` logs 3, 3, 3. Explain why, and give the smallest change that makes it log 0, 1, 2.

level: juniorimportance: must knowfreq 85%

basics

~20 s

var creates one function-scoped i shared by the whole loop, so all three deferred callbacks read the same binding, which already holds 3 by the time the timers fire. Declaring the counter with let gives each iteration its own i, logging 0, 1, 2.

open as a page

In JavaScript, why is a function's local variable not freed when that function returns, if an inner function it returned still refers to the variable?

level: juniorimportance: must knowfreq 58%

basics

~20 s

A function object stores a hidden pointer to the scope it was created in, so returning it keeps that scope reachable. Reachable memory is never collected, so the variable lives as long as the returned function does.

open as a page

In JavaScript, why does `function () { console.log('hi'); }();` throw a SyntaxError while `(function () { console.log('hi'); })();` runs, and what other forms make an immediately invoked function expression work?

level: juniorimportance: must knowfreq 62%

basics

~20 s

A statement that begins with the keyword function is parsed as a function declaration, which needs a name and cannot be invoked in place. Wrapping it in parentheses forces the parser to read it as an expression, and expressions are callable.

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 `for (let i = 0; i < 3; i++)` loop declares the variable only once in its head, yet each iteration behaves as if it has its own `i`. How does JavaScript produce that, and which iteration's binding does the `i++` update?

level: middleimportance: must knowfreq 55%

basics

~20 s

The loop creates a new environment for every iteration and copies the loop variable's current value into it. The increment then runs against the new iteration's copy, so the previous iteration's binding is never touched again and closures over it keep that iteration's value.

open as a page

Using only closures — no class syntax and no separate module file — how do you build a single JavaScript object that exposes a public API while keeping its internal state genuinely unreachable from outside, and what makes it unreachable?

level: middleimportance: must knowfreq 58%

basics

~20 s

Run a function immediately and return only the object you want callers to have. Everything declared inside the function stays in that function's scope, which no outside expression can name, so the returned methods are the only path to the state. That is the module pattern.

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

`for (var v = 0; v < 3; v++) setTimeout(() => console.log(v))` logs 3, 3, 3, but `[0, 1, 2].forEach(v => setTimeout(() => console.log(v)))` logs 0, 1, 2 — and neither uses `let`. What accounts for the difference?

level: middleimportance: should knowfreq 40%

basics

~20 s

forEach calls its callback once per element, and every call creates a new environment holding its own parameter binding for that element. The counting loop has a single shared var binding instead. Callback-based iteration gets per-iteration bindings for free, whatever declaration keyword you use.

open as a page

Before `let` existed, how did you make `for (var i = 0; i < 3; i++) setTimeout(function () { console.log(i); }, 0);` log 0, 1, 2 without changing `var`? Show the wrapper and say what actually makes it work.

level: middleimportance: should knowfreq 42%

basics

~20 s

Wrap the body in an immediately-invoked function that takes the counter as a parameter. Argument passing copies the value into a fresh parameter binding per call, so the scheduled callback closes over that copy rather than the one shared var. Function.prototype.bind achieves the same by fixing the argument.

open as a page

In JavaScript, a function loads a large object and returns a small callback that will be stored for the lifetime of the process. How do you stop that callback from retaining the large object?

level: middleimportance: should knowfreq 42%

basics

~20 s

Capture only the small value the callback needs, computing it before the callback is created, so the large object is never part of the callback's reachable graph. If it must be referenced, release the binding by assigning null once you are done with it.

open as a page

Several classic (non-module) JavaScript files loaded with plain script tags need to share one global namespace object. Explain the `var App = App || {};` plus IIFE-augmentation idiom, why it works regardless of file order, and why swapping `var` for `let` breaks it.

level: middleimportance: should knowfreq 30%

basics

~20 s

Each file writes var App = App || {}; then an IIFE that hangs its own members on App. Because var may be redeclared and is hoisted-initialized to undefined, whichever file loads first creates the object and the rest reuse it. let throws instead of reusing.

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

A reviewer finds `for (var i = 0; i < ids.length; i++) { load(ids[i]).then(r => { results[i] = r; }); }` and every response ends up at one index instead of spread across the array, leaving holes. Diagnose it, and say what you would change so this class of bug cannot recur.

level: seniorimportance: should knowfreq 38%

basics

~20 s

All the promise callbacks share the single var binding for i, and they run only after the loop has finished, when i equals ids.length. So every write lands on that one out-of-range index. Declare the counter with let, or build the array from returned values instead of index writes.

open as a page

Two inner functions are created in the same enclosing function: a short-lived one that reads a huge array, and a small one that is stored globally and never touches that array. Why can the huge array still be retained, and how do you fix it?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Both inner functions share one environment record for the enclosing scope, and that record holds every binding any inner function captures. The stored function therefore keeps the huge array reachable even though it never reads it.

open as a page

A JavaScript module is built like this: `const mod = (function () { function a() { return 'original a'; } function b() { return a(); } return { a, b }; })();`. A caller then does `mod.a = () => 'patched a';`. Why does `mod.b()` still return 'original a', and what does that tell you about what the returned object holds?

level: seniorimportance: should knowfreq 36%

basics

~20 s

Inside the wrapper, b calls the local binding a, not the property mod.a. The returned object holds a copy of the function value at return time, so replacing the property rewires only outside callers; every internal call keeps going to the original.

open as a page

A memoize() helper in JavaScript keeps its cache in a closure. What memory questions would you settle before shipping it into a long-running process?

level: principalimportance: nice to knowfreq 22%

basics

~20 s

Settle four things: whether the cache is bounded in size or age, what each entry transitively retains, whether object arguments should be held weakly so callers can be collected, and how long the memoized function itself lives.

open as a page

Now that most JavaScript ships as ES modules, where does an immediately invoked function expression still earn its place in a codebase, and where has it become noise you should delete?

level: principalimportance: nice to knowfreq 26%

basics

~20 s

Delete it wherever a module file or a block already provides the scope: wrapping a whole module body buys nothing. Keep it for code that cannot be a module — injected snippets, embeds, build-free pages — and for an async wrapper where await is otherwise illegal.

open as a page