Closures in JavaScript Explained
Answer
A closure is a function that has access to variables from its outer (enclosing) scope, even after that outer function has returned. It "closes over" the variables it needs.
How Closures Work
Basic Example
function createCounter() {
let count = 0; // Private variable
return function () {
count++; // Accesses outer scope's count
return count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
// Each call to createCounter creates a NEW closure
const counter2 = createCounter();
console.log(counter2()); // 1 (independent from counter)
Closure Memory Model
function outer() {
const message = "Hello";
function inner() {
console.log(message);
}
return inner;
}
const fn = outer();
// outer() has returned, but 'message' is still in memory
// because 'inner' (now 'fn') still references it
fn(); // "Hello"
Practical Use Cases
1. Data Privacy / Encapsulation
function createBankAccount(initialBalance) {
let balance = initialBalance; // Private!
return {
deposit(amount) {
balance += amount;
return balance;
},
withdraw(amount) {
if (amount > balance) {
throw new Error("Insufficient funds");
}
balance -= amount;
return balance;
},
getBalance() {
return balance;
},
};
}
const account = createBankAccount(100);
account.deposit(50); // 150
account.withdraw(30); // 120
console.log(account.balance); // undefined (private!)
console.log(account.getBalance()); // 120
2. Function Factories
function multiply(factor) {
return function (number) {
return number * factor;
};
}
const double = multiply(2);
const triple = multiply(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
3. Event Handlers
function setupButton(buttonId, message) {
const button = document.getElementById(buttonId);
button.addEventListener("click", function () {
alert(message); // Closure captures 'message'
});
}
setupButton("btn1", "Hello from button 1!");
setupButton("btn2", "Hello from button 2!");
4. Memoization
function memoize(fn) {
const cache = {}; // Closure captures this
return function (...args) {
const key = JSON.stringify(args);
if (key in cache) {
console.log("From cache");
return cache[key];
}
const result = fn(...args);
cache[key] = result;
return result;
};
}
const expensiveAdd = memoize((a, b) => {
console.log("Computing...");
return a + b;
});
expensiveAdd(1, 2); // Computing... → 3
expensiveAdd(1, 2); // From cache → 3
Common Closure Pitfall
// The classic loop problem
for (var i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i); // Prints: 3, 3, 3
}, 1000);
}
// Why? All callbacks share the SAME 'i' variable
// By the time they run, i is 3
// Solution 1: Use let (creates new binding each iteration)
for (let i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i); // Prints: 0, 1, 2
}, 1000);
}
// Solution 2: IIFE (creates new scope each iteration)
for (var i = 0; i < 3; i++) {
(function (j) {
setTimeout(function () {
console.log(j); // Prints: 0, 1, 2
}, 1000);
})(i);
}
Closures and Memory
// Closures can cause memory to be retained
function createLeak() {
const largeData = new Array(1000000).fill("x");
return function () {
// Even if we don't use largeData, it's retained!
return "hello";
};
}
const fn = createLeak(); // largeData stays in memory
// To fix: Set reference to null when done
fn = null; // Now largeData can be garbage collected
Module Pattern (Classic)
const Calculator = (function () {
// Private state
let result = 0;
// Private function
function validate(n) {
return typeof n === "number";
}
// Public interface
return {
add(n) {
if (validate(n)) result += n;
return this;
},
subtract(n) {
if (validate(n)) result -= n;
return this;
},
getResult() {
return result;
},
reset() {
result = 0;
return this;
},
};
})();
Calculator.add(5).add(3).subtract(2);
console.log(Calculator.getResult()); // 6
Key Points
- Closure = function + its lexical environment
- Functions remember where they were created
- Enables data privacy and encapsulation
- Used in callbacks, event handlers, memoization
- Be careful with closures in loops (use
let) - Watch for unintended memory retention