learn
JavaScript

Closures

Functions that remember their scope, private state, and the loop bug.

A closure is a function bundled with the variables around it at creation. It is the most-asked JavaScript interview topic because it explains private state, callbacks and half of all async bugs in one mechanism.

Real-life situation

Two shopping carts on one page must not share items. A cart factory returns add and count functions closing over a private items array — each cart gets its own scope, so adding to one never touches the other. No classes, no naming tricks; the scope boundary is the privacy boundary.

Seen in: React's useState closes over each render's scope, which is why late-running callbacks can see stale values (React 16.8, 2019, useState reference).

Visual

Scope chain
BrowserAPIRealtimeCacheDatabaseShareddurableephemeral

Try it

Two carts, two counters
javascriptTab moves to the next control
function createCart() {
const items = [];
return {
  add: function (item) {
    items.push(item);
  },
  count: function () {
    return items.length;
  },
};
}
const first = createCart();
const second = createCart();
first.add("book");
first.add("pen");
console.log(first.count());
console.log(second.count());
function counter() {
let n = 0;
return function () {
  n = n + 1;
  return n;
};
}
const next = counter();
console.log(next());
console.log(next());
Press Run to see the output.
Expected output
2
0
1
2
Runner helpers

sleep(ms) waits; fakeFetch(url) returns deterministic sample data. Console code runs in a worker with a 5-second limit.

Try adding a third cart and sharing one items array between two factories — watch independence break. Then convert counter to an object with reset. Each render in UI code closes over its own props the same way, which is why stale values surprise even experienced developers.

Loop with var — guess first, then reveal.

predict.js
for (var i = 0; i < 3; i = i + 1) {
setTimeout(function () {
  console.log(i);
}, 0);
}

Interview questions

What is a closure?basic

A closure is a function that remembers the variables from where it was created, even after that outer function has finished. The inner function carries a live link to those variables — not a snapshot — so later calls see the latest values. Every callback, handler and private counter in JavaScript leans on this: it is the language's built-in privacy and memory mechanism.

Interviewers also ask

  • Does the closure copy the variables or link to them?
How do closures create private state?basic

Put a variable inside a function and return inner functions that use it: outsiders can call those functions but never touch the variable directly. A cart factory returning add and total keeps items hidden — no property to poke, no global to clash. That is real encapsulation with zero syntax: the scope boundary IS the privacy boundary.

Interviewers also ask

  • How is this different from a #private class field?
Why does var in a loop break with setTimeout, and what are the fixes?intermediate

var is function-scoped, so all five iterations share ONE i; by the time the timers run, the loop has finished and every callback logs 5. Three fixes: use let (fresh binding per iteration), wrap the body in an IIFE capturing the current value, or pass i as an argument. Interviewers want the mechanism (shared binding vs per-iteration binding), not just the let spell.

Interviewers also ask

  • What does each timer log with let, exactly?
Do closures capture values or variables?intermediate

Variables — live links, not snapshots. If the outer variable changes after the closure is created, the closure sees the new value on its next call. This surprises people in loops (all callbacks sharing one var) and in effects that read "stale" state. When you need a snapshot, copy the value into a new variable (or a parameter) at capture time.

Interviewers also ask

  • How does this cause stale-state bugs in UI code?
Where do you meet closures in everyday code?basic

Everywhere a function outlives its creation: event handlers reading outer settings, setTimeout callbacks, fetch().then() chains, array mappers using outer totals, and module-level caches. If you have ever written a button handler that remembers which item it belongs to, you have shipped a closure — the interview just wants you to name the mechanism.

Interviewers also ask

  • Point to the closure in your last project.
Can closures cause memory leaks?intermediate

Yes, when a long-lived closure holds a big variable nothing else needs — a global cache of callbacks, a forgotten timer, an event listener on a removed element. The garbage collector frees only unreachable memory, and anything the closure can still reach counts as reachable. Fix: clear the timer, remove the listener, or null the reference when the feature is done.

Interviewers also ask

  • How would you find which closure holds the memory?
What is the module pattern, in one sentence each for how and why?intermediate

How: wrap code in a function (or module) and expose only chosen functions, keeping the rest in closure scope. Why: it gives private state and a tiny public surface without any class machinery — the pattern behind countless libraries before ES modules, and still the mental model for what a module boundary does.

Interviewers also ask

  • What do ES modules add on top of this?
What does a counter function remember between calls?basic

Its closed-over variable. Each call to the outer factory creates a fresh scope, so two counters from the same factory count independently — that independence is the giveaway that closures link scopes, not code. If both counters shared a number, the variable would be outside the factory, and there would be no closure doing the remembering.

Interviewers also ask

  • How would you reset one counter without touching the other?
Why do React-style effects sometimes see stale values, in closure terms?intermediate

The effect callback closes over the render's variables — a snapshot of that moment's props and state. If the effect runs later (a timer, a subscription callback), it still reads the old render's values, not the current ones. Fixes all follow the same idea: re-create the closure when values change (dependencies), read latest values through a ref, or clean up the old subscription first.

Interviewers also ask

  • What does the dependency list actually do?
Explain debounce in terms of closures.intermediate

Debounce returns a function that closes over a timer id. Each call clears the pending timer and starts a new one, so the real work runs only after calls stop for the wait period. The closed-over timer id is the entire memory of the debouncer — without the closure there would be nowhere private to keep it. Same story for throttle and once.

Interviewers also ask

  • What is the difference between debounce and throttle?

Key takeaways

  • Closures link live variables, not snapshots.
  • Factory + closure is the simplest private state in the language.
  • Shared loop bindings (var) are the classic closure bug; let fixes it.

References

Last verified: 2026-10-12.

On this page