1. Introduction
A closure is formed when a function 'remembers' the variables from its surrounding (lexical) scope, even after that outer scope has finished executing. Closures are what allow inner functions to access, and mutate, variables declared in an enclosing function. They are the foundation for patterns like private state, memoization, and function factories.
Cricket analogy: A bowler's death-over yorker plan, memorized during the powerplay team meeting, is still recalled and executed in over 20 even though that meeting ended hours ago, just as a closure remembers its enclosing scope's variables long after that scope has returned.
2. Syntax
function makeCounter() {
let count = 0; // captured by the closure
return function () {
count += 1;
return count;
};
}
const counter = makeCounter();
counter(); // 1
counter(); // 23. Explanation
When makeCounter() runs, it creates a local variable count and returns an inner function. Normally, count would be destroyed once makeCounter() finishes executing. But because the returned inner function references count, JavaScript keeps that variable alive in memory — the inner function 'closes over' it. Each call to makeCounter() creates a brand-new, independent count variable and closure.
Cricket analogy: Each time a new match starts, the scoreboard operator resets the run count to zero and that count stays alive and updates ball by ball for the whole innings — two simultaneous matches on adjacent grounds keep entirely separate, independent run tallies, just like two calls to makeCounter() each keeping their own live count variable.
Closures capture variables by reference, not by value. This means if multiple functions close over the same variable, they all see the latest value, not a snapshot from when they were created.
Cricket analogy: When the scoreboard's live run total is shared between the stadium screen and the TV broadcast graphic, both display the exact latest number after every run scored, never a stale snapshot from an earlier over, just as multiple closures sharing one variable always see its current value.
Classic gotcha: In a for loop using var, all callbacks scheduled with setTimeout share the SAME function-scoped variable, so by the time the callbacks run, the loop has already finished and every callback sees the final value. Switching the loop variable to let creates a fresh binding for each iteration, so each closure captures its own value as expected.
4. Example
function makeCounter() {
let count = 0;
return function () {
count += 1;
return count;
};
}
const counter = makeCounter();
console.log(counter());
console.log(counter());
console.log(counter());
for (var i = 1; i <= 3; i++) {
setTimeout(() => console.log("var i:", i), 0);
}
for (let j = 1; j <= 3; j++) {
setTimeout(() => console.log("let j:", j), 0);
}5. Output
1
2
3
var i: 4
var i: 4
var i: 4
let j: 1
let j: 2
let j: 36. Key Takeaways
- A closure is an inner function combined with references to its outer (lexical) scope's variables.
- Closures keep outer variables alive in memory as long as an inner function still references them.
- Each invocation of an outer function creates a new, independent set of captured variables.
- Closures capture variables by reference, so all functions sharing a closure see the latest mutated value.
- var in a loop creates one shared binding across iterations; let creates a new binding per iteration, which is why let fixes the classic setTimeout-in-a-loop bug.
Practice what you learned
1. What is a closure in JavaScript?
2. In the classic loop-with-setTimeout gotcha, why do all three callbacks in the var-based loop log the same final value?
3. How does replacing var with let fix the loop-closure bug?
4. Do closures capture variables by value or by reference?
5. In the makeCounter() example, why do counter() calls return 1, 2, then 3 instead of always returning 1?
Was this page helpful?
You May Also Like
Functions in JavaScript
Learn how to declare, invoke, and understand the hoisting behavior of JavaScript functions.
let, const and Block Scope in JavaScript
How let and const introduced block scoping, the temporal dead zone, and why const does not make objects immutable.
Higher-Order Functions in JavaScript
Learn how functions that take or return other functions power JavaScript's map, filter, and reduce, plus function factories.
setTimeout and setInterval in JavaScript
Schedule delayed or repeating code with setTimeout and setInterval, and learn about timer drift and the need to clear intervals.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics