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
function outer() {
let count = 0;
function inner() {
count++;
console.log(count);
}
return inner;
}
const counter = outer();
counter(); // 1
counter(); // 2
counter(); // 3What happens here:
outer()returnsinner;innerremembered thecountvariable from its lexical environment;- even after
outer()has finished,countdoes 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:
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 2Here 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:
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)); // 15Each 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:
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:
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: 4The 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.
function outer() {
const a = 42;
return function() {
console.log(a); // this is a closure
};
}Where closures are used in practice
| Scenario | Example |
|---|---|
| Private data | Hide variables inside a function, expose only methods |
| Function factories | Generators, configured handlers |
| Modules and encapsulation | Splitting logic without global variables |
| Caching (memoization) | Keep computed results between calls |
| Counters and state | Hold state across invocations |
| Callbacks and event handlers | Access outer data from inside a function |
| Functional programming | Currying, 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.
varinside a loop. The classic trap: every callback closes over the same variable and sees the last value.letcreates 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;thisis decided by the call site and is not captured (except in arrow functions, which take it lexically).
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.