JavaScript Closures
Closures are one of JavaScript’s most powerful and sometimes confusing features. They allow functions to access variables from their outer scope even after the outer function has finished executing. Understanding closures unlocks advanced programming techniques.
What Are Closures?
A closure is a function that “remembers” the environment it was created in. It has access to variables from its own scope, the outer function’s scope, and the global scope.
function outerFunction() {
let outerVariable = 'I am from outer scope';
function innerFunction() {
console.log(outerVariable); // Can access outerVariable
}
return innerFunction;
}
let closure = outerFunction(); // outerFunction finishes
closure(); // Still prints "I am from outer scope"
Even though outerFunction has finished, innerFunction keeps access to outerVariable.
Why Closures Matter
Closures preserve data between function calls:
function createCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
let counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
Each call to counter() increments the same count variable.
Practical Examples
Private Variables
Create private variables that can’t be accessed directly:
function createBankAccount(initialBalance) {
let balance = initialBalance;
return {
deposit(amount) {
balance += amount;
return balance;
},
withdraw(amount) {
if (amount <= balance) {
balance -= amount;
return balance;
}
return 'Insufficient funds';
},
getBalance() {
return balance;
}
};
}
let account = createBankAccount(1000);
console.log(account.getBalance()); // 1000
account.deposit(500); // 1500
account.withdraw(200); // 1300
// console.log(account.balance); // undefined - private!
Function Factories
Create functions with preset behavior:
function createMultiplier(multiplier) {
return function(number) {
return number * multiplier;
};
}
let double = createMultiplier(2);
let triple = createMultiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
Event Handlers
Closures are common in event handling:
function setupButtons() {
for (let i = 0; i < 3; i++) {
let button = document.createElement('button');
button.textContent = `Button ${i}`;
button.addEventListener('click', function() {
console.log(`Button ${i} clicked`);
});
document.body.appendChild(button);
}
}The closure captures the current value of i for each button.
Memoization
Cache expensive function results:
function memoize(fn) {
let cache = {};
return function(...args) {
let key = JSON.stringify(args);
if (cache[key]) {
return cache[key];
}
let result = fn(...args);
cache[key] = result;
return result;
};
}
function expensiveCalculation(n) {
console.log('Calculating...');
return n * n;
}
let memoizedCalc = memoize(expensiveCalculation);
console.log(memoizedCalc(5)); // Calculates and logs
console.log(memoizedCalc(5)); // Uses cache, no log
Common Pitfalls
Loop Variables
With var, this creates a problem:
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // Always 3
}, 100);
}Use let or immediately invoked function expressions (IIFE):
for (let i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // 0, 1, 2
}, 100);
}Memory Leaks
Closures can cause memory leaks if they hold references to large objects:
function createLeak() {
let largeObject = { /* big data */ };
return function() {
console.log(largeObject);
};
}Be careful with closures in long-running applications.
Closures and Scope
Closures follow lexical scoping - they look outward for variables:
let globalVar = 'global';
function outer() {
let outerVar = 'outer';
function inner() {
let innerVar = 'inner';
console.log(innerVar); // inner
console.log(outerVar); // outer
console.log(globalVar); // global
}
inner();
}Closures are fundamental to JavaScript. They’re used in modules, event handling, and many design patterns.
For more on functions, see JavaScript functions.
The MDN Closures guide explains them in detail.