Var, and let in a loop working differently

I’m running a loop to print the same variable using var and let.

for (var val = 0; val < 10; val++) {
    console.log(val);
    setTimeout(function() {
        console.log(`The number is ${val}`);
    }, 1000);
}

This above block of code log 0-9 correctly but then prints “The number is 10” ten times instead of printing “The number is 0”, “The number is 1” and so on.

However, if I change the variable declaration to ‘let’. Then it prints out correctly.

for (let i = 0; i < 10; i++) {
    console.log(i);
    setTimeout(function() {
        console.log(`The number is ${i}`);
    }, 1000);
}

This above block of code prints the statements correctly. Each statement is printed after a delay of one second like “The number is 0”, “The number is 1” and so on.

I’m not able to understand what is going on here. Can someone break it down for me?

This is because of hoisting. Declaring a variable with var in your for loop doesn’t limit its scope to just that block of code like it would using let. Since its existence extends beyond that for loop, when the value is evaluated by the setTimeout() callback, you get the last value it was set to. (it gets set to 10, which is why the for loop stops)

for (var val = 0; val < 10; val++) {
  console.log(val);
  setTimeout(function() {
    console.log(`The number is ${val}`);
  }, 1000);
}
if (val === 10) {
  console.log('I can still see the value');
}

You can see the same behavior while using let if you’ve already declared the variable before using it in your for loop.

let i;
for (i = 0; i < 10; i++) {
  console.log(i);
  setTimeout(function() {
    console.log(`The number is ${i}`);
  }, 1000);
}

Now it would behave the same way as the var for loop did.

1 Like

var is function scoped (in layman terms globally scoped) i.e. value declared using var is available throughout the program. Whereas, let is locally scoped i.e. it’s only available to a particular block. You need to make the val available locally using let within the loop for you to see the change as pointed out by @selectall .

You can also try this code of snippet to understand the difference. Difference between var and let

Thank you for the explanation @selectall and Thank you for the linking the resource to explain the difference between var and let, @pulsating_photon. However, I should mention that I do understand that let is block scoped and var is only function scoped and that they can’t be accessed outside these defined scopes. I also understand the idea that var sort of leaks out to the global scope but I’m unable to see what the association is between the two(scope and this problem) here.

What I’m more interested to understand is why do these loops behave in this way where one prints ‘The number is 10’ ten times and other prints ‘The number is 0’, ‘The number is 1’ and so on after the delay of one second. How is the function accessing the already changed values because the function is not printing it right after and the loop would continue to increment the value? What I’m failing to understand is that even though the loop is executing first and then the function is running after a delay of one second, how is it accessing value ‘0-9’ with let instead of 10 like var?

So this has indeed turned out to be a really interesting discussion. I looked into some more resources and was able to decipher the following for this question of yours.

We need to keep in mind when a JavaScript function is defined inside a loop with variables, it establishes something known as closures. So what closures in JS ensure is this: make the local lexical environment available to the inner function. A lexical environment comprises references to the variables within a loop or a function and is used when the same is invoked. Hence, when we initialize using let inside the loop, the inner function (which is an async callback function) knows that the next time there is a callback, the variable logged must be local and I need to update instantly or perform the “closure” of the variable instantly.

Whereas, when var is used, it’s scope is not block. This doesn’t trigger the async function for a change in the value and the variable is only “closed” after the loop has completed and hence the value 10, in your case.

I encourage you to look into these articles/answers: https://stackoverflow.com/questions/111102/how-do-javascript-closures-work
&&
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures

I also believe this is one of the reasons why var is now deprecated and it’s encouraged to use let as a permanent replacement to the same.

1 Like

Unless that block happens to be a function body. var does make a variable global. NOT using var (or let or const) means the variable is hoisted to the top of global scope even if defined inside a function.

1 Like

I believe I do understand what closures are now. In the case of var, since it leaked to the global scope and due to that the lexical environments don’t really have to do anything since the variable again is not limited to their scope. I guess that’s why the function always printed 10. However, if we look at let, then the function call inside the loop had a chain of outer lexical environments which it remembered and thus when called, they printed out the correct version of the variable instead of just printing 10.

And as @mtf said in this discussion, I’ll be coming back to this topic and thoroughly clear it out.

I have to say it’s only been a few weeks since I’ve joined the community but my learning has shot up exponentially ever since I did. I’m very grateful!

Thank you for the all the help, @selectall, @pulsating_photon and @mtf.

3 Likes