A JavaScript function can still access variables from where it was created, even after the outer function has finished. That continuing access is a closure: the function retains references to its surrounding lexical environment, not necessarily a frozen copy of every value.
What a closure is
MDN defines a closure as “the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment).” In practical terms, when JavaScript creates a function, that function retains access to the bindings in the scope where it was defined. If the function is called later, it can still use those bindings.
As an Amazon Associate I earn from qualifying purchases.
Consider a counter factory:
function makeCounter() {
let count = 0;
return function () {
count += 1;
return count;
};
}
const next = makeCounter();
next(); // 1
next(); // 2
Calling makeCounter() creates the count binding and returns a nested function. The outer call then finishes, but the returned function can still access and update that binding. The function does not need a new copy of count on each call; it continues to refer to the same binding.
Calling makeCounter() again creates a different environment, so the new counter has its own independent count. “Remember” is a useful shorthand for this retained access, not a claim that a function snapshots every value around it. See MDN’s JavaScript closures guide.
#1 Best Overall
How closures keep state with the code that uses it
Closures are useful when a function needs to carry state into later work, such as a callback or event handler. They also let several functions created during the same outer call share one environment. For example, a factory can return increment, decrement, and read functions that all operate on the same private counter binding.
function makeCounter() {
let count = 0;
return {
increment() {
count += 1;
return count;
},
decrement() {
count -= 1;
return count;
},
value() {
return count;
}
};
}
const counter = makeCounter();
counter.increment(); // 1
counter.increment(); // 2
counter.decrement(); // 1
counter.value(); // 1
Each method closes over the same count binding, which is why one method’s update is visible to the others. The variable is not exposed as a property of the returned object; callers interact with it through the provided operations. This is a lightweight way to keep data and the functions that use it together, not a complete security boundary.
Rank #2
Why callbacks in a var loop can all see the last value
A common closure surprise comes from callbacks created in a loop using var:
Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Repair Windows errors before they cause bigger problems3Scan for outdated or missing drivers - takes under a minutefor (var i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i);
}, 0);
}
The callbacks run after the loop has finished. Because var is function-scoped, these callbacks all access the same i binding, which the loop has changed to 3. The callbacks therefore log 3, 3, and 3. The cause is the shared changing binding; the timer merely makes it clear that the callbacks execute later.
Use let for a per-iteration binding
For this pattern, declaring the loop variable with let gives each iteration its own binding for callbacks to access:
for (let i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i);
}, 0);
}
These callbacks log 0, 1, and 2. The difference is which binding each callback closes over.
Rank #4
Use for...of or forEach when they fit the task
When iterating over values, for...of is another clear option:
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
for (const value of values) {
setTimeout(function () {
console.log(value);
}, 0);
}
For arrays, a callback passed to forEach also receives the current value as an argument:
Best Value
values.forEach(function (value) {
setTimeout(function () {
console.log(value);
}, 0);
});
Choose the loop form that matches the job. MDN’s JavaScript for reference explains loop lexical declarations and the shared-binding issue.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




