JavaScript course menu
JavaScript Closures Explained with Examples
Understand JavaScript closures: functions that remember variables from where they were created, counters and private state, function factories, closures in loops (var vs let), memoization, and practical uses in event handlers and modules.
On this page
A closure is a function together with the variables it can see from the place where it was created. The function "remembers" those variables, even after the outer function has finished running. Closures sound abstract, but you use them every time you write a callback.
A function that remembers#
function makeGreeter(greeting) {
return function (name) {
return `${greeting}, ${name}!`;
};
}
const sayHello = makeGreeter("Hello");
const sayNamaste = makeGreeter("Namaste");
console.log(sayHello("Asha"));
console.log(sayNamaste("Ravi"));Hello, Asha!
Namaste, Ravi!makeGreeter has finished by the time sayHello("Asha") runs, yet the inner function still knows greeting. Each call to makeGreeter creates a separate closure with its own greeting.
Private state: a counter#
Closures can keep data private: nothing outside can reach the variable directly, only through the functions you return:
function createCounter() {
let count = 0;
return {
increment() {
count++;
return count;
},
reset() {
count = 0;
},
get value() {
return count;
},
};
}
const clicks = createCounter();
clicks.increment();
clicks.increment();
console.log(clicks.value);
const views = createCounter();
views.increment();
console.log(views.value, clicks.value);
console.log(clicks.count);2
1 2
undefinedEach counter has its own count, and there is no way to set it to an invalid value from outside. (Classes offer a similar feature with private #fields; see Classes.)
Function factories#
A function that builds other functions, customised by its arguments:
const multiplier = (factor) => (n) => n * factor;
const double = multiplier(2);
const triple = multiplier(3);
const addGst = multiplier(1.18);
console.log(double(10), triple(10), addGst(100));20 30 118Closures capture variables, not values#
A closure keeps a live link to the variable, so it sees later changes:
let rate = 0.05;
const interest = (amount) => amount * rate;
console.log(interest(1000));
rate = 0.08;
console.log(interest(1000));50
80The classic loop question#
const withVar = [];
for (var i = 1; i <= 3; i++) {
withVar.push(() => i);
}
const withLet = [];
for (let j = 1; j <= 3; j++) {
withLet.push(() => j);
}
console.log(withVar.map((fn) => fn()));
console.log(withLet.map((fn) => fn()));[ 4, 4, 4 ]
[ 1, 2, 3 ]With var, there is one variable i for the whole loop; all three closures share it, and by the time they run it is 4. With let, each iteration gets a new j, so each closure remembers its own value.
Practical uses#
Remembering expensive results (memoization)#
function memoize(fn) {
const cache = new Map();
return (n) => {
if (cache.has(n)) return `${cache.get(n)} (cached)`;
const result = fn(n);
cache.set(n, result);
return result;
};
}
const slowSquare = (n) => n * n;
const fastSquare = memoize(slowSquare);
console.log(fastSquare(12));
console.log(fastSquare(12));144
144 (cached)Event handlers#
Event handlers in web pages are closures over the variables around them:
<button id="a">Button A</button>
<button id="b">Button B</button>
<p id="log">Click a button.</p>function setUp(id) {
let clicks = 0; // each button gets its own counter
document.getElementById(id).addEventListener("click", () => {
clicks++;
document.getElementById("log").textContent = `${id} clicked ${clicks} times`;
});
}
setUp("a");
setUp("b");Practice#
Practice exercise
Write createBankAccount(initial) that keeps the balance private and returns deposit(amount), withdraw(amount) (which refuses to go below zero and returns a message) and getBalance().
Show solution
function createBankAccount(initial) {
let balance = initial;
return {
deposit(amount) {
balance += amount;
return balance;
},
withdraw(amount) {
if (amount > balance) return "Insufficient funds";
balance -= amount;
return balance;
},
getBalance() {
return balance;
},
};
}
const account = createBankAccount(500);
console.log(account.deposit(200));
console.log(account.withdraw(1000));
console.log(account.withdraw(300));
console.log(account.getBalance());700
Insufficient funds
400
400Key takeaway#
Finished this lesson?