Loop variable capture issue in JavaScript functions - basic example

I’m encountering a puzzling issue with JavaScript where the loop variables are not functioning as I expect. This is what I am attempting to do:

var callbacks = [];
// creating 5 callback functions
for (var x = 0; x < 5; x++) {
  callbacks[x] = function() {
    console.log("Current number:", x);
  };
}
// executing each callback
for (var k = 0; k < 5; k++) {
  callbacks[k]();
}

However, instead of getting:

Current number: 0
Current number: 1
Current number: 2
Current number: 3
Current number: 4

I instead receive this output:

Current number: 5
Current number: 5
Current number: 5
Current number: 5
Current number: 5

The same issue arises when I use click handlers:

var links = document.querySelectorAll('a');
for (var x = 0; x < links.length; x++) {
  links[x].onclick = function() {
    console.log("Link index:", x);
  };
}
<a href="#">First</a>
<a href="#">Second</a>
<a href="#">Third</a>

This also occurs with asynchronous actions:

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

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

It even happens with various loop types:

const numbers = [10, 20, 30];
const handlers = [];

for (var idx in numbers) {
  handlers.push(() => console.log("position:", idx));
}

for (var val of numbers) {
  handlers.push(() => console.log("amount:", val));
}

for (const item of numbers) {
  var record = { value: item };
  handlers.push(() => console.log("item:", item, "record:", JSON.stringify(record)));
}

handlers.forEach(h => h());

What can I do to resolve this common predicament?

yeah, this got me too when i started. all your functions point to the same x variable, which ends up being 5 after the loop finishes. use bind() to fix it:

callbacks[x] = function(num) {
  console.log("Current number:", num);
}.bind(null, x);

works every time and you don’t have to mess with closure stuff.

Had this exact bug in a dropdown menu last month - drove me crazy for hours! Simple trick is wrapping your function with another function that captures the current value:

for (var x = 0; x < 5; x++) {
  callbacks[x] = (function(currentX) {
    return function() {
      console.log("Current number:", currentX);
    };
  })(x);
}

The outer function runs immediately and locks in x’s current value as currentX.

This happens because of JavaScript closures and variable hoisting. All your functions reference the same variable x which ends up being 5 after the loop finishes.

Quick fix - use let instead of var:

for (let x = 0; x < 5; x++) {
  callbacks[x] = function() {
    console.log("Current number:", x);
  };
}

Or create a closure with an IIFE:

for (var x = 0; x < 5; x++) {
  callbacks[x] = (function(index) {
    return function() {
      console.log("Current number:", index);
    };
  })(x);
}

Honestly though, I deal with these callback headaches constantly. Rather than wrestling with closures and timing issues, I just automate everything through Latenode.

When I need multiple events or callbacks with proper variable scoping, I set up workflows that handle the data flow correctly from the start. No more debugging closure issues or worrying about variable references.

Latenode handles execution context properly so each callback gets the right values. Way cleaner than remembering all these JavaScript gotchas.

Been there. This closure mess hits everyone eventually.

The problem is your functions all point to the same memory spot. When they finally run, that spot has the last loop value.

Yeah, you can fix it with let, bind, or forEach. But manually managing callback chains and variable scope is a recipe for bugs.

I gave up fighting JavaScript’s closure weirdness years ago. Now I build these callback workflows in Latenode. It handles variable binding and timing without the headache.

Need dynamic callbacks or event handlers with different data? Set up a workflow that processes each item right. No more debugging shared variables or wrong timing.

Latenode handles execution context so each callback gets what you expect. Way less frustrating than memorizing JavaScript scope rules.

Closure traps like this taught me to think about when functions execute vs. when they’re created. Your callbacks get created during the loop but run later, so they all see x’s final value.

Array methods with proper parameter passing saved me on a project last year:

var callbacks = Array.from({length: 5}, function(_, index) {
  return function() {
    console.log("Current number:", index);
  };
});

The underscore is the unused array element, index gives you position. Each function gets its own scope right away - no shared variable problem. Works on older browsers too, which mattered for that client.

Hit this same issue two years back while building a dynamic menu system. The problem is var uses function scope, not block scope - so all your callbacks end up sharing the same variable reference.

I switched to forEach instead of regular for loops and it fixed everything:

[0,1,2,3,4].forEach(function(x) {
  callbacks[x] = function() {
    console.log("Current number:", x);
  };
});

Each iteration creates its own execution context, so every callback gets its own variable copy. Way more readable than IIFEs and works consistently across different JS engines. forEach handles the scoping naturally without extra syntax or needing ES6.