DriversRecommendedOutdated drivers can make a good PC feel brokenScan driver issues before chasing fixes manually.Scan NowOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsSlow PC?RecommendedPC slow today? Run a repair scan before it gets worseResolve common Windows issues and optimize system performance.Scan Now×
Skip to content
World desk4 min

Fixing JavaScript Loops That Log the Same Value

Delayed JavaScript callbacks can share one changing var binding and all read its final value. Use a per-iteration binding or capture the current item.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

If delayed callbacks created inside a JavaScript loop all log the same value, they are usually reading the same changing var binding after the loop has advanced or finished. For a counter loop, put let in the for initializer so each iteration gets a binding of its own. For collection iteration, for...of or forEach can make the per-item value clearer.

Why the callbacks see the same value

A closure lets a function access bindings in the surrounding environment. It does not necessarily save a frozen copy of every variable’s value when the function is created. If several callbacks close over one binding and that binding changes before they run, they read its later value.

As an Amazon Associate I earn from qualifying purchases.

For example, with for (var i = 0; i < 3; i++), var does not create a new loop-local binding for each pass. If each pass schedules a delayed callback that logs i, the loop can finish before any callback runs. All three callbacks then read the same binding, whose value is 3, and log 3. MDN demonstrates this behavior in its JavaScript for statement reference and closures guide.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

This is about when the callback runs, not only which loop syntax appears in the code. A callback that runs immediately, before the counter changes, can observe a different value from one that runs later, such as a callback passed to setTimeout.

Choose a fix that matches what the callback needs

Situation Suitable approach Why it helps
A delayed callback needs the numeric counter from each pass Declare let in the for initializer Each iteration has a binding for closures to observe.
A delayed callback needs each value from a collection Use for...of with const or let The callback closes over that iteration’s item binding.
Callback-based collection iteration fits the task Use forEach Each callback receives the current item as an argument.
Legacy code must keep a var loop Capture the current item in a per-iteration function scope The callback closes over a function parameter rather than the shared counter.

Fix a counter loop with let

Declare let i in the loop initializer, rather than declaring it once outside and updating that same variable. In a for loop, JavaScript creates a per-iteration binding that callbacks can observe.

for (let i = 0; i < 3; i++) {
  setTimeout(() => {
    console.log(i);
  }, 1000);
}
// Logs 0, 1, 2

The delay in this example is one second; the important point is that the callbacks run later and each sees its own iteration’s counter. MDN explains the special per-iteration behavior of let in the for initializer in its reference.

Use an iteration value for collection callbacks

for...of

When the callback needs the current item rather than a numeric index, iterate over the items directly:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
for (const item of items) {
  schedule(() => console.log(item));
}

Each pass binds item for that iteration. Use const when the binding itself should not be reassigned; it does not prevent mutation of an object that the binding refers to.

forEach

If a callback-based iteration suits the surrounding code, the item can be passed directly to the forEach callback:

items.forEach((item) => {
  schedule(() => console.log(item));
});

Both patterns are documented alternatives to the shared-var-binding pattern in MDN’s closures guide. Pick based on whether the code needs a counter, an item, or the callback form already used by the surrounding code.

Keep var in legacy code by capturing the current value

If the loop must retain var, create a function scope on each pass and pass the current item into it. The delayed callback then closes over the function parameter, which does not change when the loop counter advances.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
for (var i = 0; i < items.length; i++) {
  (function (item) {
    schedule(() => console.log(item));
  })(items[i]);
}

This pattern is a workaround for code written before block-scoped let and const; MDN describes it in the closures guide.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Check where the variable is declared

Changing the keyword to let does not automatically create a new binding per iteration. This still uses one shared variable because i is declared outside the loop:

let i = 0;
for (; i < 3; i++) {
  setTimeout(() => console.log(i), 1000);
}

The callbacks can all log 3. Move the declaration into the initializer—for (let i = 0; ...)—or capture the current item in a per-iteration binding. The declaration’s location is what changes the behavior in this case, as MDN’s loop reference illustrates.

How to diagnose the issue in your code

  1. Check whether the callback runs later. Look for scheduling functions or other asynchronous work. If the callback runs after the loop has continued, it may observe a later value.
  2. Find the binding the callback reads. Check whether it is a var declared in the loop or a variable declared once outside and updated by the loop.
  3. Decide whether the callback needs an index or an item. Use let in the counter loop initializer for a per-iteration index; use an item-oriented pattern when the item is what matters.
  4. Apply the matching fix and check the outputs. For the three-pass delayed counter example, per-iteration bindings produce 0, 1, and 2, rather than three copies of the final counter.

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.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More from the Wire

  1. World desk4 min
    How to Spot an AI Voice Scam Before Sending MoneyDon’t rely on how a caller sounds. Pause, call back through a known number, and verify the emergency with another trusted person before sending money.
  2. Mountain View desk4 min
    Google’s SynthID Detector: How to Check AI-Generated Images, Video and AudioGoogle’s SynthID Detector looks for an embedded watermark in supported images, video and audio. Here is what its results do—and do not—show.
  3. Redmond desk20 min
    How to create a link to File or Folder in Windows 11Windows 11 gives you several ways to point to a file or folder without moving or duplicating it. You can create a desktop shortcut,…
Recommended PC Tool
Recommended PC Tool
Crashes, No Sound, or Screen Glitches?Free driver scan
PC Slower Than It Used to Be?Free scan - under a minute

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.