JavaScript Closure Table

PatternWhat closes overField note
counter factorya private count variableThe state is unreachable from outside - closures ARE encapsulation before classes
loop with leteach iteration's bindinglet makes a NEW binding per pass; var makes ONE that all callbacks share (i = last value)
event handler setupthe element + configOne closure per element - mind memory: detached nodes with closures resist GC
memoize(fn)the cache objectThe cache lives as long as the returned function - a closure IS a one-object store
once(fn)a called flagFlag flips on first call; every later call short-circuits - init logic in one line
partial applicationthe leading argsbind() and arrow chains both close over leading args - config becomes specialization
module pattern (IIFE)the private API surfacePre-ESM privacy: return the public face, close over the internals
stale loop var (var)the single shared bindingThe classic bug: ten timers all log 10 - fix with let, not with more closures
Reference: the MDN closures reference. A closure is a function PLUS the variables it captured from where it was defined - the inner function keeps those bindings alive even after the outer function returned. That one rule produces private state (counter factories), per-iteration capture (let in loops), memoization caches and once() guards without any class syntax. Bottom line: closures capture VARIABLES, not values - the var-in-loop bug is ten callbacks sharing one binding, and let fixes it by making ten bindings. The cost is memory: every closure pins its captured scope, so detached DOM nodes referenced by long-lived handlers resist garbage collection. Related tools: this table (closures capture variables, never this), hoisting table (what exists when the closure is created), and iterator table (generators close over their own state).

A closure is a function plus the variables it captured from its birthplace - the inner function keeps those bindings alive even after the outer function has returned. That single mechanic produces private state, memoization caches, once() guards and partial application without any class syntax.

Bottom line: closures capture VARIABLES, not values. The famous var-in-loop bug is ten callbacks sharing ONE binding (so they all log the last value), and let fixes it by creating ten bindings - one per iteration. Everything else on this table follows from the same capture rule.

The honest part: closures cost memory. Every closure pins its captured scope against garbage collection, and a long-lived event handler that closes over a DOM node keeps that node alive after it is detached from the document - the classic slow leak in single-page apps.

How to use

  1. Make private state: a factory function returns an inner function that reads and writes a local - nothing outside can reach the variable.
  2. Loop with let, never var, when callbacks need their own iteration: for (let i = 0; ...) setTimeout(() => console.log(i)) logs 0,1,2 - var logs the last value three times.
  3. Guard init logic with once(): a returned function checks a captured flag, runs the work one time, and short-circuits every later call.

Frequently asked questions

What exactly does a closure capture - a value or a reference?

A reference to the variable binding, not a snapshot of the value. If the outer code changes the variable after the closure is created, the closure sees the change - closures observe the variable, they do not photograph it. This is why ten setTimeout callbacks created with var all print the same final number (one shared binding, mutated ten times), while let creates a fresh binding per iteration and each callback keeps its own. To freeze a value deliberately, capture it through a function call or an explicit copy inside the closure.

How are closures different from classes for private state?

Both hide state; they differ in cost and shape. A closure gives you ONE private state box per function instance - perfect for a counter, a cache or a once() guard, with zero ceremony. A class gives you named fields and methods shared on the prototype - better when state has structure and behavior grows. Pre-ES2022, closures were also the only real privacy in JavaScript (#private fields changed that); the practical rule is closures for single-purpose state, classes for structured state, and never a class with one field and one method - that is a closure wearing a suit.

Why can closures cause memory leaks in single-page apps?

A closure pins every variable it captured against garbage collection. Attach an event handler that closes over a DOM node, then remove the node from the document without removing the handler - the node stays alive because the handler's scope still references it, and if the handler itself is never unregistered, the whole captured chain lives as long as the page. The fix is cleanup discipline: removeEventListener on teardown, or use the once option and AbortController signals so the browser drops the handler and its captured scope together.

When do I actually need a closure instead of just passing arguments?

When state must SURVIVE between calls or when configuration must be fixed in advance. Passing arguments is stateless - each call stands alone. A memoize wrapper needs the cache to live between calls; a partial application needs the leading arguments fixed while the rest arrive later; debounce and throttle need their timer state to persist across invocations. Anywhere you would otherwise reach for a module-level let 'just to store something between calls', a closure gives you the same persistence with tighter scope and no global surface.

Related tools