A Function That Remembers
JavaScript Closures
When a function references variables from an outer scope, it forms a closure — it remembers those variables even after the outer function has returned.
What you'll learn
- Recognize a closure
- Use closures to make private state
- Avoid the loop-variable closure pitfall
A closure is a function plus the variables it remembers from the scope where it was created. Closures are how a function “keeps state” between calls.
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 What’s happening:
makeCounterdeclarescountand returns an inner function.- The inner function references
count— it forms a closure over the variable. - Each time we call
counter(), the samecountis updated. - The outside world can’t access
countdirectly — it’s “private”.
Private State Without Classes
Closures are the original way to encapsulate state in JavaScript.
function makeAccount(initial) {
let balance = initial;
return {
deposit: (n) => (balance += n),
withdraw: (n) => (balance -= n),
getBalance: () => balance,
};
}
const acct = makeAccount(100);
acct.deposit(50);
console.log(acct.getBalance()); // 150
acct.withdraw(30);
console.log(acct.getBalance()); // 120
// console.log(acct.balance); // undefined — no direct access Closures Capture the Variable, Not Its Value
This trips people up. The closure captures the variable binding, not a snapshot of the value at definition time.
function makeBoth() {
let value = 0;
return {
get: () => value,
set: (v) => (value = v),
};
}
const both = makeBoth();
console.log(both.get()); // 0
both.set(42);
console.log(both.get()); // 42 ← same `value` shared by both functions get and set both close over the same value. Updating it through
one is visible to the other.
The Classic var Closure Trap
A for loop with var shares the same loop variable across all
iterations. Closures created inside the loop all point at the same
var.
const handlers = [];
for (var i = 0; i < 3; i++) {
handlers.push(() => console.log(i));
}
handlers[0](); // 3 😱
handlers[1](); // 3 😱
handlers[2](); // 3 😱 All three closures share the same i, which is 3 by the time the
loop ends. Fix: use let, which gives each iteration its own i.
const handlers = [];
for (let i = 0; i < 3; i++) {
handlers.push(() => console.log(i));
}
handlers[0](); // 0
handlers[1](); // 1
handlers[2](); // 2 This single difference — let over var — eliminates a whole class
of bugs that plagued JavaScript for over a decade.
Closures Are Everywhere
You’ve been using closures without realizing:
- “
setTimeout(() => doSomething(x), 1000)— the callback closes overx.” addEventListener("click", () => updateUI(state))— closes overstate.array.map((item) => item * multiplier)— closes overmultiplier.
Closures are foundational. Once they click, they make a lot of JavaScript patterns clearer.
Try It Yourself
Exercise
A counter factory
// declare makeCounter here
const a = makeCounter();
console.log(a(), a(), a());
const b = makeCounter();
console.log(b());
💡 Show hint
✅ Show solution
function makeCounter() {
let count = 0;
return function () {
count++;
return count;
};
}
const a = makeCounter();
console.log(a(), a(), a());
const b = makeCounter();
console.log(b());
Up Next
A function calling itself — recursion.
JavaScript Recursion →