For loop and var

Can anyone please clarify why the output of this

for (var i=0; i<2; i++){ setTimeout(function(){ console.log(i); },0); } //OUTPUT 2 2

But the output of this is

for (let j=0; j<2; j++){ setTimeout(function() { console.log(j); },0); } //OUTPUT 0 1

as expected

1 Like

This is actually a really interesting thing and one I’m not totally firm on, but I’ll explain to the best of my understanding and hopefully someone else can jump in and fix any errors I make. A way to see the behaviour a bit clearer is to modify your code slightly to have different console.logs.

for (var i=0; i<2; i++){ console.log("i outer: ", i) setTimeout(function(){ console.log("i inner: ", i); },0); } //OUTPUT 2 2 for (let j=0; j<2; j++){ console.log("j outer: ", j) setTimeout(function() { console.log("j inner: ", j); },0); } //OUTPUT 0 1

Variables: var and let

I believe the behaviour seen here is a result of the call stack, plus the difference between how javascript parses var and let.

So first, var creates a globally-scoped variable unless used within a function. Therefore by a process in JS called Hoisting if a variable is declared using var and isn’t contained within a function specifically, then it it declared immediately and available throughout the script. Conversely, using let (and also const) to declare variables scope those variables to the block. The key difference here is that now loops, if statements and so on constitute blocks for let and const where they didn’t for var, demonstrated below.

var a = true; if (a) { let a = 3; var b = 4; console.log(a); } for (let i = 0; i < 2; i++) { console.log(a); console.log(b); }

Note how in the above codebyte, inside of the if block we log a to be 3, however inside of the for block we log it to be true, as the let declaration isn’t available outside of the if. Also notice that the variable b declared using var instead is accessible within the for block. Hopefully that demonstrates the above properly.


The call stack

Now we know how the variables work, but what about the call stack as I mentioned? Well, The call stack is basically what allows Javascript to execute the code in the correct order. Javascript is known as a single-threaded language, which means that essentially Javascript has a single call stack that runs everything. The simple explanation is that it cannot run more than one thing at once like a multi-threaded process would do. So when a Javascript file is loaded by an interpreter, the call stack kicks in, and executes the code in the following order.

  1. Run all stand-alone code.
  2. Load up functions in order of calling.
  3. If when running a function another function call is nested within this function, that inner call is added to the top of the stack.

This ensures that JS can keep track of what’s been run, and most importantly execute the same way every single time to increase reliability. Say we had the below code:

function print_one() {
    console.log(1);
}
function print_two() {
    console.log(2);
}
function print_three() {
    print_two();
    console.log(3);
}
function main() {
    print_one();
    print_three();
}
main();

The order this would run in the call stack is:

  1. Add the main() function to the top of the call stack and begin execution.
  2. Reach print_one() and add this to the top of the call stack, then execute printing 1 to the console.
  3. Continue running main, and reach print_three(), adding this to the top of the call stack and executing.
  4. Inside of print_three() reach print_two(), executing this first and printing 2 to the console.
  5. Return to print_three() printing 3 to the console.
  6. Return to main(), ending the function and clearing the call stack.

Therefore we would end up printing

1
2
3

to the console due to how the call stack works.


All together now

Now how does this all tie in to solve this problem? Well, lets return to our initial codebyte:

for (var i=0; i<2; i++){ console.log("i outer: ", i) setTimeout(function(){ console.log("i inner: ", i); },0); } //OUTPUT 2 2 for (let j=0; j<2; j++){ console.log("j outer: ", j) setTimeout(function() { console.log("j inner: ", j); },0); } //OUTPUT 0 1

OUTPUT

i outer:  0
i outer:  1
j outer:  0
j outer:  1
i inner:  2
i inner:  2
j inner:  0
j inner:  1

So first thing to note is how all the outers are printed before a single inner is printed. This comes from the call stack. Since the first step is to run all standalone code, what happens is the for loops are run, printing immediately and adding each function call to the bottom of the call stack, meaning no function calls will be executed until all standalone code is run. So just for example, the execution for the i for loop is:

  1. set i = 0.
  2. log i (0) to the console.
  3. Add the setTimeout() function call to the call stack.
  4. set i = 1.
  5. log i (1) to the console.
  6. Add the setTimeout() function call to the call stack.
    We can see clearly that this will be the same for the j for loop also.

So this explains why we print the outers and inners for each in the order we do, but why are the numbers wrong for the i inners? Well, this is where the var section comes in. Since we declared i using the var keyword, this is not contained to the for loop and is hoisted to become a global variable, accessible in the entire script. Therefore when we are modifying the value of i, this is being saved as a global variable and is therefore not being reset once we reach our function calls. So once our i setTimeout() functions are actually called, i already has the value of 2 as the for loop has finished execution, and i is a global variable.

However when the j setTimeout() functions are called, j is a local variable to the scope of the for loop, and has no value once the for loop ends essentially. Thus JS handles this by effectively saving the value of the local variable at each function call as part of the call stack. This means that when the setTimeout() functions for the j loop are finally called at the very end of the code, the value of j when it was called has been recorded, and the function can output the correct value, thus giving the results we have.


Conclusion

Apologies for this response being massive, but this is a relatively complex thing that’s hard to see visually in the execution of JS code and I felt a lot of background and information was needed to make this clear. If anything needs cleared up please let me know and I’ll be happy to elaborate further in the replies. This is a super interesting quirk of how variable declarations work in JS and can give you an appreciation for why each keyword (var, let, const) has it’s place.

4 Likes

I’m not the author of the question, but thank you for your great explanation.

1 Like

That was mightily well explained, much appreciated. I believe I get it now. I do however have a question in regard to the outer codes all printing before the inner code. In the example you gave regarding the call stack, it is apparent a function is executed -if possible once it is added to the call stack. Case in point

By that logic why does this

act differently. Following the procedure you enumerated I would expect the timeout function to be executed logging the value of i to the console. Could you please explain why it acts differently or am I missing something? Thanks in advance

The important part to get is that functions come after non-function code always. In the case of this, the non-function code is:

for (var i=0; i<2; i++){
  console.log("i outer: ", i)
}

for (let j=0; j<2; j++){
  console.log("j outer: ", j)
}

Therefore this is always actually executed before any function calls are executed no matter where those calls are in the code. In this example the way I would layout the call stack order is:

  1. All base code.
  2. setTimeout() for i=0. (using i=0 to demonstrate exactly which one is being run however as discussed in my original post i is not equal to 0 at the point this is run)
  3. setTimeout() for i=1.
  4. setTimeout() for j=0.
  5. setTimeout() for j=1.

The important thing to note is that when JS is being run, it will always call functions last, in the order they have been called. If you have a function call within another function then the call stack purely goes in order of code lines, at least as long as you are within another function. The reason being that in JS’s eyes, as soon as you are running a function you are at the “function” part of the call stack, meaning that any function calls from then on take priority. It’s a little confusing but hopefully that makes sense.

I may be misunderstanding you but the output of this is a bit different from the proposed logic

for (var j=0; j<2; j++){
    console.log("j outer: ", j)
    yup();
  }

  function yup (){
    console.log('yeah')
  }

OUTPUTS

j outer:  0
yeah
j outer:  1
yeah

Ah okay I see what you mean! Then yes, setTimeout() works differently in the sense that it is automatically thrown to the back of the queue, whilst that function call is treated as part of the in-line code.