Closures are the concept interviewers reach for when they want to know whether you understand how JavaScript actually works, rather than which APIs you have memorised. The good news is that almost every closure question comes from one small idea. Get that idea right and the trick questions stop being tricks.
What a closure actually is
A closure is a function together with the variables it references from the scope where it was defined. When you create a function inside another function, the inner function keeps a live link to the outer function’s variables — even after the outer function has returned and, by every other measure, finished running. The variables do not disappear when the outer call ends; they stay alive for as long as some inner function can still reach them.
The mental model worth carrying into the interview is this: a function remembers where it was born, not where it is called. Its scope is decided by its position in the source code (lexical scope), fixed the moment it is defined. You can pass that function anywhere — into a timer, an event listener, another module — and it still reads and writes the same variables it grew up around.
Captured by reference, not by value
The single detail that produces most of the confusion: a closure captures the variable, not a snapshot of its value at the moment the closure was created. If the variable changes later, the closure sees the new value, because it is looking at the same binding, not a copy.
That is exactly what you want for a counter that increments, and exactly what surprises people when several closures share one variable. So when a closure “returns the wrong number,” the question to ask is never “what was the value?” but “which variable am I bound to, and who else can change it?”
The loop bug everyone hits
The canonical example: a for loop that uses var to create three callbacks, each of which logs the loop index. People expect 0, 1, 2. With var they get 3, 3, 3.
The reason follows directly from capture-by-reference. var is function-scoped, so there is exactly one i shared by every iteration and every callback. By the time the callbacks actually run — after the loop has finished — that one i has reached its final value. All three closures point at the same binding, and that binding now holds 3.
Switching to let fixes it because let is block-scoped: each iteration gets a fresh binding of i, so each closure captures a different variable. The pre-let fix was to wrap the body in an immediately-invoked function that took i as an argument — the argument was a new variable per call, which is the same trick let now does for you. If you can explain why both fixes work in terms of “how many bindings exist,” you have demonstrated the whole concept.
What closures are actually good for
Closures are not a puzzle feature; they are how a lot of everyday JavaScript is built:
- Private state. Variables in the outer scope are reachable only through the functions you return. That is data privacy with no
#fields and no classes — the factory function returns methods that share a value nobody outside can touch. - Factory functions. A function that returns a configured function — a
multiplier(2)that returns a doubler — works because the returned function closes over the configuration you passed in. - Memoization and caching. Keep a cache object in the outer scope; the returned function closes over it and reuses results across calls.
- Event handlers and callbacks. A handler defined inside a component keeps access to that component’s variables when it fires later, which is why closures and asynchronous code are so tightly linked.
- Once, debounce, and throttle. Each keeps private bookkeeping — a “have I run?” flag, a timer id, a last-call timestamp — in a closure so the returned function can consult it.
The gotcha to name out loud
Because capture is by reference, closures can keep large objects alive longer than you expect — a handler that closes over a big array will keep that array in memory until the handler itself is unreachable. In long-lived single-page apps, closures that outlive their usefulness are a common source of memory leaks. Mentioning that trade-off unprompted is a strong senior signal: it shows you know closures are a live link, not free magic.
The interview signal
You are almost certainly being asked about closures whenever a question involves var inside a loop, a setTimeout that logs something surprising, “implement a counter / private variable without a class,” or “why does this function still have access to that value?” The answer always routes back through the same two facts: lexical scope decides what a function can see, and capture is by reference to a living variable. Say those two sentences, then apply them to the specific code, and you will reason your way through any variation they throw at you.
Ready to drill it? Work the JavaScript interview questions — the closure, scope, and event-loop questions there are where this model earns its keep — or browse all questions.