Chapter 1 — Closures
📖 Definition
A closure is a function that "remembers" the variables from the lexical scope in which it was created, even after that outer scope has finished executing.
🔍 Explanation
When a function is created in JavaScript, it carries a hidden reference ([[Environment]]) to the variable environment in which it was defined. When invoked later — even from a completely different scope — it can still access those original variables.
Closures power:
- Data privacy (private state)
- Currying and partial application
- Memoization and caching
- Event handlers and callbacks
- React hooks internally
💻 Code Example — Basic Counter
function createCounter() {
let count = 0; // private variable
return function increment() {
count += 1; // closes over `count`
return count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
const counter2 = createCounter();
console.log(counter2()); // 1 — independent closure💻 Code Example — Private State (Module Pattern)
function createBankAccount(initial) {
let balance = initial; // truly 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 acc = createBankAccount(100);
acc.deposit(50); // 150
acc.withdraw(30); // 120
acc.getBalance(); // 120
// acc.balance → undefined ✅ inaccessible from outside💻 Code Example — Once-Only Function
function once(fn) {
let called = false;
let result;
return function (...args) {
if (!called) {
called = true;
result = fn.apply(this, args);
}
return result;
};
}
const initApp = once(() => console.log("App initialized"));
initApp(); // "App initialized"
initApp(); // (nothing)
initApp(); // (nothing)💻 Code Example — Currying with Closures
function multiply(a) {
return function (b) {
return function (c) {
return a * b * c;
};
};
}
multiply(2)(3)(4); // 24
// Or generic curry:
const curry = (fn) => {
return function curried(...args) {
if (args.length >= fn.length) return fn.apply(this, args);
return (...next) => curried(...args, ...next);
};
};
const sum = (a, b, c) => a + b + c;
curry(sum)(1)(2)(3); // 6💻 Code Example — Classic Loop Pitfall
// Bug: all callbacks see the final i
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
// Output: 3, 3, 3
// Fix 1: block-scoped let creates new binding per iteration
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0);
}
// Output: 0, 1, 2
// Fix 2: IIFE to capture the value
for (var i = 0; i < 3; i++) {
(function (j) {
setTimeout(() => console.log(j), 0);
})(i);
}
// Output: 0, 1, 2🌍 Real-World Uses
- React
useStatesetter closes over a slot in the fiber. - Express middleware closes over config:
app.use(authMiddleware(config)). - Memoization caches:
const memo = {}; return (key) => memo[key] ??= compute(key);
🎯 Likely Interview Questions
- Explain closure with a real example.
- What's the output of the classic
setTimeoutloop? (See above.) - How can closures cause memory leaks?
A closure holds references to its outer scope. If a long-lived object (event listener, global handler) closes over a large data structure, GC cannot collect it.
- What are some real uses of closures? — Data privacy, currying, memoization, function factories.