Day 4 · Concept 10
Closures & scope
A function "closes over" the variables in scope when it was defined. The variables stay alive as long as the function does. This single mechanism powers callbacks, modules, currying, and event handlers.
1 · The simplest closure
function makeCounter() {
let count = 0;
return function() {
count += 1;
return count;
};
}
const c = makeCounter();
console.log(c()); // 1
console.log(c()); // 2
console.log(c()); // 3
const d = makeCounter(); // fresh count
console.log(d()); // 1count doesn't disappear when makeCounter returns. The returned
function still holds a reference. That reference keeps count alive — and
private to this counter.
2 · Private state via closure
function makeBank(initial) {
let balance = initial;
return {
deposit(n) { balance += n; },
withdraw(n) {
if (n > balance) throw new Error("insufficient");
balance -= n;
},
balance: () => balance,
};
}
const acct = makeBank(100);
acct.deposit(50);
acct.withdraw(30);
console.log(acct.balance()); // 120
// acct.balance is NOT directly accessible — it's locked inside the closure3 · The classic var trap
// What it looks like — and the bug
const fns = [];
for (var i = 0; i < 3; i++) {
fns.push(() => i); // 'i' is function-scoped — shared!
}
console.log(fns.map(f => f())); // [3, 3, 3]
// With let — block-scoped, fresh per iteration
const fns2 = [];
for (let i = 0; i < 3; i++) {
fns2.push(() => i);
}
console.log(fns2.map(f => f())); // [0, 1, 2]The reason "never var" exists. Block-scoping with let avoids the trap entirely.
4 · Currying — closures applied
const multiply = a => b => a * b;
const double = multiply(2);
const triple = multiply(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
// Useful for callbacks
const items = [1, 2, 3];
console.log(items.map(multiply(10))); // [10, 20, 30]5 · Memory considerations
Closures hold references to their enclosing scope. If a long-lived closure (an event handler) captures a big object, the object can't be garbage collected. Watch for this in React effects or long-running timers — null out references you don't need.
6 · Common mistakes
- The
var-in-loop trap. Uselet. - Stale closures in React.
useEffectcaptures the variables at render time. Use refs or include in the deps array. - Heavy captures. A closure capturing a 50-MB object keeps it alive — be mindful in long-lived contexts.
7 · When it clicks
- You see "function returns function" and instantly know it's closure-based state.
- You spot memory leaks from over-capturing.
- Currying and partial application feel natural rather than clever.
Found this useful?