What Is a Closure?
A closure is a function that retains access to its outer (enclosing) scope even after that outer function has returned. In plain English: a function "closes over" the variables in the environment where it was created — hence the name.
This is not a quirk or a bug. It's a fundamental part of how JavaScript's lexical scoping works, and understanding it unlocks a huge range of patterns used throughout real codebases.
A Simple Example
function makeCounter() {
let count = 0;
return function () {
count++;
return count;
};
}
const counter = makeCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
Here, makeCounter returns an inner function. Even though makeCounter has finished executing, the inner function still has access to count. That's a closure in action — count is kept alive in memory because the inner function references it.
Why Closures Exist: Lexical Scope
JavaScript uses lexical scoping, which means the scope of a variable is determined by where it's written in the source code — not where a function is called. When a function is defined, it captures a reference to the scope chain at that point. This captured scope is the closure.
Practical Use Cases
1. Data Privacy / Encapsulation
Closures let you simulate private variables without classes:
function createBankAccount(initialBalance) {
let balance = initialBalance;
return {
deposit: (amount) => { balance += amount; },
withdraw: (amount) => { balance -= amount; },
getBalance: () => balance,
};
}
const account = createBankAccount(100);
account.deposit(50);
console.log(account.getBalance()); // 150
The balance variable is completely inaccessible from outside — only the returned methods can touch it.
2. Function Factories
You can use closures to generate specialized functions on the fly:
function multiplier(factor) {
return (number) => number * factor;
}
const double = multiplier(2);
const triple = multiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
3. Event Handlers and Callbacks
Closures are everywhere in async code and DOM event handling, where inner functions need to "remember" values from the time they were set up — such as loop indices or configuration options.
A Common Closure Pitfall
The classic var-in-a-loop bug catches many developers off guard:
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Logs: 3, 3, 3
Because var is function-scoped, all three callbacks share the same i. By the time the timeouts fire, the loop has finished and i is 3. The fix: use let, which creates a new binding per iteration.
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Logs: 0, 1, 2
Key Takeaways
- A closure is a function paired with its surrounding lexical environment.
- Closures keep referenced variables alive even after the outer function returns.
- They enable data encapsulation, function factories, and stateful callbacks.
- Understanding closures is essential for debugging async code and writing clean, modular JavaScript.
Closures aren't magic — they're a logical consequence of how JavaScript handles scope. Once you internalize that, you'll start seeing them everywhere and writing better code for it.