Closures
A closure is a function that "remembers" the variables from the scope where it was created, even after that scope has finished executing. In other words, a closure is a function together with the lexical environment in which it was born.
Theory
TL;DR
- A closure appears when a function is declared inside another function and uses that function's variables.
- The outer function may finish, but the captured variables are not destroyed while something still references them.
- A closure keeps a reference to the variable, not a copy of it, so state survives between calls.
- Every call to the outer function creates a new, independent environment with its own variables.
- It is the main way to build private data, counters, function factories and memoization.
- The classic trap:
varin a loop together withsetTimeoutgives one shared variable to every iteration.
Quick example
function outer() {
let count = 0; // a variable of the outer function
function inner() {
count++;
console.log(count);
}
return inner;
}
const counter = outer(); // we call the outer function
counter(); // 1
counter(); // 2
counter(); // 3What happens:
outer()runs and creates the variablecountand the inner functioninner.- It returns
inner, which remembers where it was created. - Even after
outer()finishes, the variablecountis not destroyed, becauseinnerstill references it.
The chain in short:
outer() -> created count -> inner() -> remembered countA closure is a function plus its environment
Every closure stores a lexical environment (scope), that is, the set of variables it has access to.
function makeAdder(x) {
return function (y) {
return x + y; // access to "x" from the outer scope
};
}
const add5 = makeAdder(5);
console.log(add5(10)); // 15
console.log(add5(7)); // 12Explanation:
- The function
makeAddercreates and returns a new function. - The returned function closes over the value of
x(in our case5). - Even after
makeAdderfinishes,xstays alive.
What matters is that makeAdder(5) and makeAdder(10) produce two different functions with two different environments that know nothing about each other.
Private variables and state
Closures are the standard way to make data unreachable from outside.
function createCounter() {
let count = 0;
return {
increment() { count++; },
decrement() { count--; },
get() { return count; }
};
}
const counter = createCounter();
counter.increment();
counter.increment();
console.log(counter.get()); // 2Here count is not reachable directly from outside, but it is reachable from the closed-over functions (increment, decrement, get). This is how you create private variables in JavaScript.
A closure keeps a reference, not a value
A function remembers not the value but a reference to the variable in the outer environment.
function make() {
let value = 1;
return () => console.log(value++);
}
const f = make();
f(); // 1
f(); // 2, the variable "value" is alive between callsThat is exactly why the counter works: both calls read and update the same memory slot.
The classic loop mistake
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1000);
}
// one second later: 3, 3, 3Why:
- All the functions closed over one and the same variable
i, declared withvarin the function scope. - By the time the timer fires, the loop has finished and
iis already3.
The fix is let, which creates a fresh variable on every iteration:
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1000);
}
// 0, 1, 2Summary and analogy
| What it is | Example |
|---|---|
| A function that "remembers" variables from the place where it was created | return function () { console.log(a); } |
| The variables live on even after the outer function has finished | Yes |
| It creates private data and state | Yes |
| Widely used in callbacks, event handlers and modules | Yes |
A closure is like a backpack in which a function carries along all the variables it needs from the place where it was born.
Common mistakes
- Thinking a closure copies the value. It holds a reference to the variable, so a later change to that variable is visible inside.
- Using
varin a loop with an asynchronous callback. You get the final counter value on every iteration. Uselet, or wrap the body in a function. - Assuming every call to the outer function shares one state. The opposite is true: each call creates a separate environment, so two counters are independent.
- Creating closures in loops over thousands of items for no reason. Each one keeps its environment in memory, and if the reference is never released the data is not collected by the garbage collector, which is a memory leak.
- Confusing closures with
this. A closure captures variables, notthis: in a regular functionthisis determined by how it is called, and only an arrow function takesthisfrom the surrounding scope.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.