JavaScript Closures Table

PieceWhat it doesField note
function outer() { let x = 1; return function() { return x; } }The closure formThe inner function keeps x alive after outer returns
let make = outer(); make()Invoke the kept statex survives - the frame is retained, not copied
function counter() { let n = 0; return () => ++n; }Private staten is invisible from outside - the pre-class privacy
for (var i = 0; i < 3; i++) setTimeout(() => console.log(i))The var loop bugPrints 3,3,3 - var is one shared binding
for (let i = 0; ...) setTimeout(...)Prints 0,1,2let makes a fresh binding per iteration
IIFE capturePer-iteration capture (old fix)(function(j) { ... })(i) - the pre-let workaround
Reference: the MDN closures guide. A closure is a function bundled with the variables it references from its defining scope - those variables stay alive as long as the function does. This is how JavaScript makes private state: a counter function's n cannot be read or reset from outside, only incremented through the returned closure. Bottom line: closures power module patterns, event handlers that remember their element, and every callback that touches an outer variable (which is most of them - if your callback reads an outer let, that is a closure working). The one trap is var in loops: one shared binding means every callback sees the final value - let fixes it by making a fresh binding per iteration. Related tools: array methods table (callbacks that close over state), classes table (the #private alternative), and error table for the scope errors closures prevent.

A closure is a function bundled with the variables it references from where it was defined - those variables stay alive as long as the function does, invisible from outside. The table below is the working six: the basic form, the counter as private state, the var loop bug everyone meets once, and the fixes in historical order.

Bottom line: closures are how JavaScript makes private state without classes - a counter's count cannot be read or reset except through the returned function. And they are already everywhere: every callback that touches an outer variable is a closure working. If your setTimeout in a loop prints the final value three times, that is a closure working TOO - with var's one shared binding; let fixes it by making a fresh binding per iteration.

The honest part: the mental model beats memorization. Functions remember the BINDING, not the value - so closures see updates (two closures sharing a counter see each other's increments). And closures hold memory: everything the captured scope references stays alive, which is why long-lived handlers capturing large objects are a leak pattern. Capture deliberately, drop listeners when done.

How to use

  1. Spot the closure: any function that reads a variable from an outer scope that has already returned.
  2. Use the counter pattern for private state that only your methods can touch - the pre-class answer that still fits small jobs.
  3. Fix loop-and-timeout bugs with let (one binding per iteration); recognize the IIFE version as the pre-let museum piece.

Frequently asked questions

What exactly is a closure in JavaScript?

A function plus the variables from its defining scope that it keeps referencing. When the outer function returns, its local variables would normally die - but if the returned function references them, the engine keeps that scope alive. The inner function carries its birthplace with it: wherever you call it later, the variables it closed over are the ones from its creation, updated with any changes made since.

How do closures create private state?

The counter pattern: function counter() { let n = 0; return { inc: () => ++n, get: () => n }; } - n exists only inside the closure. Nothing outside can read it directly, reset it, or even confirm its type; the returned methods are the only doors. Before #private fields, this was THE privacy mechanism, and it still fits when the state is small and one factory function is cleaner than a class.

Why does setTimeout in a loop print the same value three times?

Because var declares ONE variable for the whole function - the loop's three timeouts all close over that single binding, and by the time they run, the loop has finished and the binding holds 3. let in the loop header creates a fresh binding per iteration, so each callback closes over its own copy and prints 0, 1, 2. The pre-let workaround wraps each iteration in an IIFE that captures the current value as a parameter - the museum piece that explains old code.

Do closures cause memory leaks?

They can hold memory deliberately, which becomes a leak when the held data is huge and the closure lives long. A closure keeps its entire defining scope reachable - capture one variable and the engine may retain the whole scope object, including the 2MB buffer next to it. The discipline: in long-lived handlers (event listeners on document, intervals), capture only what is needed, drop listeners with removeEventListener or AbortSignal when done, and null out large references the closure no longer needs.

Related tools