JavaScript is a versatile programming language that offers a lot of flexibility to developers. However, there is one feature of JavaScript that can cause some confusion when it comes to loops and scoping. In this article, we will explore this feature and provide some tricks to work around it using var and let declarations.

Let’s start with an example:

const operations = []

for (var i = 0; i < 5; i++) {
 operations.push(() => {
 console.log(i)
 })
}

for (const operation of operations) {
 operation()
}

In this code, we have a loop that iterates 5 times and adds a function to an array called operations. This function logs the loop index variable i. Later, we run these functions.

The expected result of this code should be:

0
1
2
3
4

However, the actual result is:

5
5
5
5
5

Why is this happening? It’s because of the use of var declarations.

Since var declarations are hoisted, the code above is equivalent to:

var i;
const operations = []

for (i = 0; i < 5; i++) {
 operations.push(() => {
 console.log(i)
 })
}

for (const operation of operations) {
 operation()
}

In the for-of loop, i is still visible and it’s equal to 5. Therefore, every reference to i in the function is going to use this value.

So, how can we solve this issue? The simplest solution is to use let declarations. Introduced in ES6, let declarations help avoid some of the pitfalls of var declarations.

Let’s change var to let in the loop variable:

const operations = []

for (let i = 0; i < 5; i++) {
 operations.push(() => {
 console.log(i)
 })
}

for (const operation of operations) {
 operation()
}

Now, the output will be:

0
1
2
3
4

How is this possible? This works because on every loop iteration, i is created as a new variable each time, and every function added to the operations array gets its own copy of i.

It’s important to remember that in this case, you cannot use const because there would be an error as for tries to assign a new value in the second iteration.

Another way to solve this problem, which was common in pre-ES6 code, is by using an Immediately Invoked Function Expression (IIFE). In this approach, you can wrap the entire function and bind i to it. By creating a function that immediately executes and returns a new function, we can execute it later:

const operations = []

for (var i = 0; i < 5; i++) {
 operations.push(((j) => {
 return () => console.log(j)
 })(i))
}

for (const operation of operations) {
 operation()
}

In conclusion, JavaScript’s behavior with var declarations in loops can lead to unexpected results in terms of scoping. By using let declarations or IIFEs, we can ensure that variables are scoped correctly and avoid any confusion. Remember to always choose the appropriate approach based on your specific use case.