Simple practical example of JavaScript closure within loops

var funcs = [];
// let's create 3 functions
for (var i = 0; i < 3; i++) {
  // and store them in funcs
  funcs[i] = function() {
    // each should log its value.
    console.log("My value:", i);
  };
}
for (var j = 0; j < 3; j++) {
  // and now let's run each one to see
  funcs[j]();

The output is this:

My value: 3
My value: 3
My value: 3

However, I'd like it to produce:

My value: 0
My value: 1
My value: 2


The same issue arises when a delay in executing the function is due to event listeners:

var buttons = document.getElementsByTagName("button");
// let's create 3 functions
for (var i = 0; i < buttons.length; i++) {
  // as event listeners
  buttons[i].addEventListener("click", function() {
    // each should log its value.
    console.log("My value:", i);
  });
<button>0</button>
<br />
<button>1</button>
<br />
<button>2</button>

… or using asynchronous code, such as with Promises:

// A function for async wait
const wait = (ms) => new Promise((resolve, reject) => setTimeout(resolve, ms));

for (var i = 0; i < 3; i++) {
// Log i once each promise resolves.
wait(i * 100).then(() => console.log(i));

This challenge can also be seen in for in and for of loops:

const arr = [1,2,3];
const fns = [];

for (var i in arr){
fns.push(() => console.log(“index:”, i));
}

for (var v of arr){
fns.push(() => console.log(“value:”, v));
}

for (const n of arr) {
var obj = { number: n }; // or new MyLibObject({ … })
fns.push(() => console.log(“n:”, n, “|”, “obj:”, JSON.stringify(obj)));
}

for(var f of fns){
f();

What is the fix for this common issue?

When dealing with closures inside loops, a common challenge in JavaScript is that variables are bound to the same function scope. This results in unexpected outcomes such as those seen in both for loops and asynchronous callbacks.

To resolve this, you can use several approaches such as block-scoping using let, or immediately-invoked function expressions (IIFE). Let’s explore these solutions.

Using let Keyword for Block Scope

The let keyword creates block-scoped variables, meaning each iteration of the loop will have its own instance of the variable.

Code Example:

let funcs = [];
for (let i = 0; i < 3; i++) {
  funcs[i] = function() {
    console.log("My value:", i);
  };
}
for (let j = 0; j < 3; j++) {
  funcs[j](); // My value: 0, My value: 1, My value: 2
}

Here, let i = 0 creates a new scope for every iteration of the for loop, so each function captures the value of i correctly.

Using Immediately-Invoked Function Expressions (IIFE)

If you’re unable to use let (perhaps due to legacy code), you can opt for IIFE. This approach ensures each iteration’s i value is enclosed in its own function scope.

Code Example:

var funcs = [];
for (var i = 0; i < 3; i++) {
  (function(index) {
    funcs[index] = function() {
      console.log("My value:", index);
    };
  })(i);
}
for (var j = 0; j < 3; j++) {
  funcs[j](); // My value: 0, My value: 1, My value: 2
}

Here, the IIFE creates a closure for each i, preserving its unique loop value.

Handling Asynchronous Code

For asynchronous functions, such as those using promises, the let keyword efficiently resolves the issue.

Code Example:

const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));

for (let i = 0; i < 3; i++) {
  wait(i * 100).then(() => console.log(i));
}

The let keyword ensures that each asynchronous call references a distinct i value. This will log 0, 1, and 2 after delays of 0ms, 100ms, and 200ms, respectively.

Applying these strategies ensures that each function or asynchronous operation captures the correct loop variable state, increasing the reliability and correctness of your JavaScript code in such scenarios.

Hey there! Here’s a quick fix. Use let instead of var to ensure block scope:

let funcs = [];
for (let i = 0; i < 3; i++) {
  funcs[i] = function() {
    console.log("My value:", i);
  };
}
funcs.forEach(f => f());

For asynchronous tasks, let works as well:

const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
for (let i = 0; i < 3; i++) {
  wait(i * 100).then(() => console.log(i));
}

This captures the correct loop variable.