Closures
The Variables a Function Never Forgets
LinkedIn Hook
You write a function inside another function. The inner one "remembers" variables from the outer one — even after the outer function has returned and its execution context is destroyed.
How? That's a closure. And it's the single most powerful concept in JavaScript.
Closures power React hooks. They power data privacy in factory functions. They're why the classic
forloop withsetTimeoutbreaks withvarbut works withlet. They're behind every module pattern you've ever used.But here's what most tutorials miss: closures aren't magic. They're a natural consequence of lexical scope — the rule that says "where you write a function determines what variables it can access."
In this lesson you'll learn the precise definition of a closure, build a private bank account with one, and fix the notorious
forloop +setTimeoutbug three different ways.Read the full lesson -> [link]
#JavaScript #Closures #InterviewPrep #Frontend #CodingInterview #WebDevelopment #JSFundamentals
What You'll Learn
- The precise definition of a closure and why every JS function technically is one
- How closures enable data privacy, factory functions, and event handlers that remember state
- How to fix the classic
forloop +setTimeoutbug withlet, IIFE, or the third-arg trick - Why closures capture references to variables, not snapshots of values
The Graduation Model
A closure is when a function "remembers" the variables from its lexical scope even after the outer function has returned and its execution context has been popped off the call stack.
Think of it this way: you graduate from university, but you still remember everything you learned there. The university (outer function) is "gone" from your daily life, but the knowledge (variables) stays with you (inner function). That's a closure.
Every function in JavaScript forms a closure. But the term is most meaningful when an inner function is returned or passed somewhere else and still accesses outer variables.
Basic Closure Example
function createCounter() {
let count = 0; // this variable is "enclosed" in the closure
return function () {
count++;
console.log(count);
};
}
const counter = createCounter();
counter(); // 1
counter(); // 2
counter(); // 3
// count is NOT accessible from outside
// console.log(count); // ReferenceError
What's happening?
createCounter()runs, createscount = 0, returns the inner functioncreateCounter()'s execution context is destroyed (popped from call stack)- But
countis NOT garbage collected — the returned function still references it - Every time we call
counter(), it accesses and modifies the samecount
Closure for Data Privacy
Closures are the foundation of data privacy in JavaScript. Before classes with #private fields, closures were the only way to create truly private variables.
function createBankAccount(initialBalance) {
let balance = initialBalance; // private -- no one can touch this directly
return {
deposit(amount) {
if (amount <= 0) throw new Error("Deposit must be positive");
balance += amount;
return `Deposited $${amount}. Balance: $${balance}`;
},
withdraw(amount) {
if (amount > balance) throw new Error("Insufficient funds");
balance -= amount;
return `Withdrew $${amount}. Balance: $${balance}`;
},
getBalance() {
return `$${balance}`;
}
};
}
const account = createBankAccount(100);
console.log(account.getBalance()); // "$100"
console.log(account.deposit(50)); // "Deposited $50. Balance: $150"
console.log(account.withdraw(30)); // "Withdrew $30. Balance: $120"
console.log(account.balance); // undefined -- truly private!
Factory Functions with Closures
function createMultiplier(multiplier) {
// multiplier is enclosed in the closure
return function (number) {
return number * multiplier;
};
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
const tenTimes = createMultiplier(10);
console.log(double(5)); // 10
console.log(triple(5)); // 15
console.log(tenTimes(5)); // 50
// Each function has its OWN closure with its OWN multiplier value
The Classic Interview Problem — setTimeout in a Loop
// BROKEN: var -- single shared variable
for (var i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i);
}, 1000);
}
// Output: 3, 3, 3
// FIX #1: Use let (creates new scope per iteration)
for (let i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i);
}, 1000);
}
// Output: 0, 1, 2
// FIX #2: Use IIFE to create a closure (the old-school way)
for (var i = 0; i < 3; i++) {
(function (j) {
setTimeout(function () {
console.log(j);
}, 1000);
})(i);
}
// Output: 0, 1, 2
// FIX #3: Use setTimeout's third parameter
for (var i = 0; i < 3; i++) {
setTimeout(function (j) {
console.log(j);
}, 1000, i);
}
// Output: 0, 1, 2
Closures with Event Handlers
function setupButtons() {
let clickCount = 0;
document.getElementById("myBtn").addEventListener("click", function () {
clickCount++;
console.log(`Button clicked ${clickCount} times`);
});
// The event handler closes over clickCount
// Every click increments the same clickCount variable
}
setupButtons();
Tricky Closure Example — Multiple Closures Share the Same Scope
function createFunctions() {
let result = [];
for (var i = 0; i < 3; i++) {
result.push(function () {
return i;
});
}
return result;
}
const funcs = createFunctions();
console.log(funcs[0]()); // 3 (not 0!)
console.log(funcs[1]()); // 3 (not 1!)
console.log(funcs[2]()); // 3 (not 2!)
// All three closures share the SAME `i` variable (var is function-scoped)
// By the time any of them runs, the loop is done and i = 3
Closure Over References, Not Values
function example() {
let x = 1;
function inner() {
console.log(x); // closes over the REFERENCE to x, not the value 1
}
x = 100; // modify x after inner is created
inner(); // 100 (not 1!)
}
example();
This is critical to understand: closures capture variable references, not snapshots of values.
Common Mistakes
- Believing closures capture values at creation time. They capture references — if the variable changes later, the closure sees the new value.
- Using
varinside aforloop withsetTimeoutand expecting each callback to see its own iteration value. All callbacks share the samei; uselet(or an IIFE) to get a fresh binding per iteration. - Over-relying on closures for huge data structures in long-lived callbacks. The enclosed variables can't be garbage collected until the closure itself is unreachable — a common source of memory leaks.
Interview Questions
Q: What is a closure in JavaScript? Give a one-sentence definition.
A closure is a function bundled together with its lexical environment, so it retains access to the variables of its enclosing scope even after that scope has finished executing.
Q: How do closures enable data privacy?
By declaring variables inside an outer function and returning only methods that reference them. The outer function's variables are unreachable from the outside, but the returned methods can still read and write them via the closure — giving you private state with a controlled public API (the pattern behind
createBankAccount).
Q: Fix the classic for loop + setTimeout problem. Explain 3 different approaches.
(1) Replace
varwithlet— each iteration gets its own fresh block-scoped binding ofi. (2) Wrap the body in an IIFE that takesias a parameter, creating a new function scope per iteration. (3) UsesetTimeout's third argument, which is passed to the callback on invocation, sidestepping the closure entirely.
Q: Do closures capture values or references?
Closures capture references to variables, not their values. If the variable changes after the closure is created but before it's called, the closure will see the updated value. This is why the
forloop withvarprints the final value ofifor all iterations.
Q: Does every JavaScript function form a closure?
Technically yes — every function carries a reference to its outer lexical environment. In practice we only call it a "closure" when an inner function is returned, stored, or passed somewhere and continues to use those outer variables after the outer function has returned.
Quick Reference — Cheat Sheet
CLOSURES -- QUICK MAP
Definition:
closure = function + its lexical environment
-> keeps access to outer variables after outer returns
Capture rule:
Captures REFERENCES, not snapshot values.
If outer variable changes later, closure sees new value.
Top use cases:
- Data privacy (createBankAccount)
- Factory functions (createMultiplier)
- Event handlers (clickCount)
- Module pattern (IIFE)
- Fixing for+setTimeout (let / IIFE / 3rd arg)
for-loop fix cheat:
var -> shared i, prints final value N times
let -> fresh i per iteration (preferred)
IIFE -> pass i as parameter
setTimeout(fn, ms, i) -> i passed to callback
Previous: Scope Chain -> The Variable Lookup Ladder Next: Block Scope vs Function Scope -> Where Your Actually Matters
This is Lesson 2.3 of the JavaScript Interview Prep Course — 14 chapters, 87 lessons.