Skip to main content

What closures are used for

A closure is a function that "remembers" its lexical (outer) scope even when it runs outside of that scope. In other words, a function can reach the variables of the place where it was created, not of the place where it is called, and that single property is what private data, function factories, caches and most functional programming patterns are built on.

Theory

TL;DR

  • A closure is the pair "function + the lexical environment it was created in".
  • The inner function holds a reference to the outer environment, so those variables do not vanish when the outer call returns.
  • Main uses: private data, function factories, state between calls, memoization, currying.
  • Closures are created automatically; you do not have to do anything special.
  • A closure keeps a reference to the variable, not a snapshot of its value.
  • Unneeded captured references keep memory alive, so large objects in a captured scope are a real leak risk.

Quick example

javascript
function outer() { let count = 0; function inner() { count++; console.log(count); } return inner; } const counter = outer(); counter(); // 1 counter(); // 2 counter(); // 3

What happens here:

  • outer() returns inner;
  • inner remembered the count variable from its lexical environment;
  • even after outer() has finished, count does not disappear;
  • that is a closure, the "saved memory" of a function.

Private data and encapsulation

Variables of the outer function are unreachable from outside but available to every function created inside it. That gives private state without classes and without # fields:

javascript
function createUser(name) { let score = 0; return { getName() { return name; }, addScore() { score++; }, getScore() { return score; } }; } const user = createUser('Oleh'); user.addScore(); user.addScore(); console.log(user.getName(), user.getScore()); // Oleh 2

Here score is a private variable: user.score does not exist, nothing outside can read or corrupt it, yet addScore and getScore have full access. The module pattern is built the same way: only the public API is returned, everything else stays in the captured scope.

Function factories and configured handlers

A closure lets you bake configuration into a function at creation time:

javascript
function makeMultiplier(factor) { return function(num) { return num * factor; }; } const double = makeMultiplier(2); const triple = makeMultiplier(3); console.log(double(5)); // 10 console.log(triple(5)); // 15

Each created function has its own factor and remembers which multiplier it was created with. Currying, partial application and composition rest on the same mechanism: applying some arguments early is just returning a function that has closed over them.

Keeping state: event handlers and memoization

An event handler outlives the function that subscribed it and still sees that function's variables:

javascript
function setupButton(id) { let clicks = 0; document.getElementById(id).addEventListener('click', () => { clicks++; console.log(`Clicks on button ${id}: ${clicks}`); }); } setupButton('save'); setupButton('cancel');

Every handler remembers its own clicks variable, even after setupButton has returned. The same trick gives a cache that survives across calls:

javascript
function memoize(fn) { const cache = {}; return function(arg) { if (cache[arg]) { console.log('From cache:', arg); return cache[arg]; } const result = fn(arg); cache[arg] = result; console.log('Computed:', arg); return result; }; } const square = memoize(x => x * x); square(4); // Computed: 4 square(4); // From cache: 4

The cache object exists only inside the closure: it is unreachable from outside and does not pollute the global scope.

How it works under the hood

When a function is created, the engine builds a Lexical Environment for it: an internal object holding the variables declared in that scope plus a reference to the outer environment. If an inner function uses an outer variable, it keeps a reference to that environment, so the garbage collector cannot reclaim it while the function is alive. That is exactly why the outer function's variables remain reachable after it returns.

You never have to opt in: a closure is formed every time you declare a function inside another one.

javascript
function outer() { const a = 42; return function() { console.log(a); // this is a closure }; }

Where closures are used in practice

ScenarioExample
Private dataHide variables inside a function, expose only methods
Function factoriesGenerators, configured handlers
Modules and encapsulationSplitting logic without global variables
Caching (memoization)Keep computed results between calls
Counters and stateHold state across invocations
Callbacks and event handlersAccess outer data from inside a function
Functional programmingCurrying, partial application, compose

Common mistakes

  • Assuming a closure copies the value. It holds a reference to the variable: change the variable after the function was created and the function sees the new value.
  • var inside a loop. The classic trap: every callback closes over the same variable and sees the last value. let creates a fresh binding per iteration and fixes it.
  • Ignoring memory. A captured scope lives as long as the function does. A handler that closed over a big object or a DOM node keeps them alive until it is removed.
  • Creating heavy closures in hot code. Each factory call builds a new environment and a new function object; inside a million-iteration loop that shows up in profiles.
  • Confusing closures with this. Closures are about variables; this is decided by the call site and is not captured (except in arrow functions, which take it lexically).

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.