What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more
If callbacks created in a JavaScript loop all log the same value, they are probably reading the same variable binding after the loop has changed it. The usual fix for a counter loop is to declare let in the for initializer; for collections, for...of or forEach can give each callback its own iteration item.
Why do callbacks from a loop log the same value?
A closure lets a function access bindings in the surrounding lexical environment. When a loop creates callbacks that run later, those callbacks may share one changing binding. By the time they execute, the loop may have advanced or finished, so each callback reads the same final value.
For example, in for (var i = 0; i < 3; i++), var does not create a separate counter binding for each pass. If each pass schedules a delayed callback that logs i, all three callbacks can run after the loop and log 3. MDN illustrates this behavior in its JavaScript for statement reference and its closures guide.
How do you fix a JavaScript setTimeout loop?
Declare the counter with let in the loop initializer
For a numeric counter that each delayed callback must remember, put let directly in the for initializer:
#1 Best Overall
for (let i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}
// Logs 0, 1, 2
A let declaration in the for initializer receives per-iteration bindings that callbacks can observe. The placement matters: it is not enough to use the let keyword if the variable is declared once outside the loop.
Use for...of when callbacks need collection items
If you are iterating over a collection and the callback needs the current item rather than a numeric index, use a per-iteration item binding:
Rank #2
for (const item of items) {
schedule(() => console.log(item));
}
const prevents reassignment of that iteration’s binding; it does not prevent mutation of an object referenced by the binding.
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Fix the driver behind crashes, sound loss and screen glitches3Clear out junk files and repair common Windows errorsUse forEach when callback-based iteration fits
For collection work that suits the array callback pattern, forEach is another option:
items.forEach((item) => {
schedule(() => console.log(item));
});
MDN documents for...of and forEach as alternatives to the classic var-based closure pattern in its closures guide.
For legacy var code, capture the current value in a function scope
If older code must retain var, pass the current item into a function scope so the callback closes over that value:
Rank #4
for (var i = 0; i < items.length; i++) {
(function (item) {
schedule(() => console.log(item));
})(items[i]);
}
This is a documented workaround for code written before block-scoped let and const.
Recommended Free Tools
Why can let still produce the same value?
let is block-scoped, but callbacks still share a binding if the variable is declared only once outside the loop and then updated repeatedly:
Best Value
let i = 0;
for (; i < 3; i++) {
setTimeout(() => console.log(i), 1000);
}
// Logs 3, 3, 3
Move the declaration into the for initializer to get a binding for each iteration, or capture the current item in a per-iteration binding. MDN shows the shared-binding case alongside the per-iteration behavior in its for statement reference.
Which fix should you choose?
- Numeric counter needed: use
letin theforinitializer. - Collection item needed: choose
for...of, orforEachif its callback-based iteration fits your code. - Legacy code must keep
var: create a function scope that receives the current value. - Callback runs synchronously: it may run before the loop changes the binding, so the delayed-callback failure pattern may not apply. Check when the callback actually executes.
These are alternatives for different code shapes, not a single rule for every codebase. The key question is whether each callback needs a distinct iteration value or whether sharing one changing binding is intended. For more on the language fundamentals behind these patterns, see MDN’s JavaScript language overview.
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.

