Loop variable closure issue in JavaScript - why functions capture wrong values

I’m having trouble with JavaScript closures when creating functions inside loops. Here’s what happens when I try to make an array of functions:

var callbacks = [];
// creating 3 callback functions
for (var counter = 0; counter < 3; counter++) {
  // storing each function in the array
  callbacks[counter] = function() {
    // expecting each to print its own counter value
    console.log("Counter is:", counter);
  };
}
for (var k = 0; k < 3; k++) {
  // executing each callback
  callbacks[k]();
}

This prints:

Counter is: 3
Counter is: 3
Counter is: 3

But I want it to print:

Counter is: 0
Counter is: 1
Counter is: 2

The same issue happens with event handlers:

var elements = document.querySelectorAll(".my-button");
for (var counter = 0; counter < elements.length; counter++) {
  elements[counter].onclick = function() {
    console.log("Button number:", counter);
  };
}
<div class="my-button">First</div>
<div class="my-button">Second</div>
<div class="my-button">Third</div>

And with async operations:

const delay = (time) => new Promise(resolve => setTimeout(resolve, time));

for (var counter = 0; counter < 3; counter++) {
  delay(counter * 50).then(() => console.log(counter));
}

How can I fix this closure problem so each function captures the correct loop variable value?

classic closure trap! u can also use bind for event handlers: elements[counter].onclick = function(index) { console.log("Button number:", index); }.bind(null, counter); - bind locks the counter value. array methods like forEach avoid this issue since they create their own scope.

The issue arises because var is function-scoped, meaning after the loop ends, counter retains its final value of 3, which all closures reference. To address this, switch to let, as it creates a new binding for each iteration. For instance:

var callbacks = [];
for (let counter = 0; counter < 3; counter++) {
  callbacks[counter] = function() {
    console.log("Counter is:", counter);
  };
}

If you need compatibility with older environments, consider using an IIFE to capture the value:

var callbacks = [];
for (var counter = 0; counter < 3; counter++) {
  callbacks[counter] = (function(capturedCounter) {
    return function() {
      console.log("Counter is:", capturedCounter);
    };
  })(counter);
}

This method also works effectively for event handlers and asynchronous functions.

Yeah, this tripped me up hard when I started with JavaScript. The problem is all your functions share the same variable reference instead of grabbing the value when they’re created. You can fix this with forEach instead of a regular for loop:

[0, 1, 2].forEach(function(counter) {
  callbacks[counter] = function() {
    console.log("Counter is:", counter);
  };
});

forEach creates a new execution context each time through, so counter gets captured properly. I like this approach when you’re working with existing arrays rather than just counting. Same thing works for your async example - forEach captures each value correctly without needing let or IIFE tricks.