Closures
A closure is created when a function remembers and continues to access variables from its enclosing scope, even after the outer function has finished executing.
How Closures Work
When an inner function is returned from its parent function, it maintains access to the parent’s variables:
function counter(step = 1) {
let count = 0;
return function increaseCount() {
count += step;
return count;
};
}
const incBy1 = counter();
const incBy10 = counter(10);
console.log(incBy1()); // 1
console.log(incBy1()); // 2
console.log(incBy10()); // 10
console.log(incBy10()); // 20
In this example, increaseCount forms a closure over count and step. Even though counter has finished executing, the returned function still has access to these variables. Each call to counter() creates a new, independent closure with its own count.
Creating Private Variables
Closures let us keep data private. We can create a variable that can only be read or changed through the functions we return:
function createAccount() {
let balance = 0;
return {
deposit(amount) {
balance += amount;
},
withdraw(amount) {
balance -= amount;
},
getBalance() {
return balance;
},
};
}
const account = createAccount();
account.deposit(100);
account.withdraw(30);
console.log(account.getBalance()); // 70
console.log(account.balance); // undefined (not accessible)
The balance variable is private. It can only be accessed through the returned methods. This gives us the same thing private fields give us in object-oriented languages.
Multiple Independent Closures
Each call to the factory function creates a separate closure:
const acc1 = createAccount();
const acc2 = createAccount();
acc1.deposit(50);
acc2.deposit(100);
console.log(acc1.getBalance()); // 50
console.log(acc2.getBalance()); // 100
What Closures Are Used For
We use closures for:
- Data encapsulation: Create private variables that cannot be accessed directly
- State preservation: Maintain state between function calls without global variables
- Callback functions: Preserve context in asynchronous operations and event handlers
- Functional patterns: Enable currying and partial application (covered in the next section)
A Common Mistake with Closures in Loops
Here is a mistake people make often:
for (var i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i);
}, 100);
}
// Prints: 3, 3, 3 (not 0, 1, 2)
All three functions share the same i variable, which equals 3 after the loop completes. Fix this by using let (which creates a new binding per iteration) or by creating a new closure:
for (let i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i);
}, 100);
}
// Prints: 0, 1, 2
For more on this, see MDN’s closure documentation.