DEV Community

naveen-karthick
naveen-karthick

Posted on

How Javascript For loops are so Complicated(well not really :p )

Ever wondered how the simplest thing you thought in programming can turn out to be so much complicated if you dig into what really happens under the hood. Yes Something as simple as a for loop could have so much intricacies and today i will explain how.
hey guys I am naveen karthick and this is my first ever article i am publishing online . So recently i saw a video from Google chrome developers explaining how complex, Javascript for loops can get and how we don't pay much attention to all of "what happens under the hood ".However I personally felt that the complications weren't explained so properly in the video so i thought why not make a article to explain stuff in the simplest way possible.Like they would say "if you don't know to explain it simpler you haven't understood it well enough".So here goes

Let's start with a simple for loop

for(var i=0;i<3;i++) {

console.log(i);

}

now the above is an ideal for-loop right where the output would be

0
1
2

But let's get to see how this for loop actually works under the hood.Our general understanding of the for loop would be
Step 1 ==> declare var i;
Step 2 ==> assign i=0;
Step 3 ==> Check the condition
Step 4 ==> execute the body;
Step 5 ==> execute the expression;
and from there on Step 3,4,5 would repeat until Step 3 returns false.
Now The above for loop executes just the way we visualize these above steps in our mind.
But now let's change our code to use Closures.

Closure :-
In the simplest terms Closure is a concept in javascript where when a function A's definition is to return a function B and even when the called function A's execution is over ,the returned function B still has access to the variables declared in the scope of function A from where it was returned from .I know i am complicating this closure concept But read the sentence above again until it makes sense or if things aren't still clear i would recommend to check out a youtube video explaining Closures as there are tons out there and closure are just not in scope of this post.

So back to the forloop,let me change the for loop to add closure

for(var x=0;x<3;x++) {

setTimeOut(() => console.log(x),1000);

}

Now if I were to execute the above for loop what would you think I'll get ?.Well if you were to say just
0
1
2
Well even i thought the same but let me explain how we are wrong but if you have got it right then "well done you have understood closures,Scope of var and how async javascript works together".

Now Coming back the output you would see in the console would be
3
3
3

but wait i know what you would be thinking When x gets incremented to 3 it wouldn't even come inside the loop then how would 3 be logged to the console.
Well let's first examine what is a var.

Var :-
Declaring a variable as var makes it function scoped which means the variable
1.) Can be read throughout the function where it was declared .
2.) Any changes done to the variable inside the function where it was declared would be reflected throughout that function .
But here it is not declared as part of a function so it would just refer to the global function where the script runs.

Now since we have understood var if we go back to the for loop here's what is happening under the hood .The for loop would run for three times as we would expect and every time the setTimeout Function is called to call the callback function we have written which is just to log the value of x to the console after a 1 second delay.The setTimeOut function executes asynchronously in a separate thread as part of the web api in the chrome browser .

Just a "it's finally over meme"

(Wait what ? People used to say Javascript is single threaded ! Well there are some excellent videos on youtube explaining why Javascript is not so single threaded or at least to the context of where Javascript runs and I am not going to go through those stuff right now as again it is just out of scope of this post :P ).Getting back .... As now you can see since the variable is var(function scoped) and when the callback function executes to print out the value of x , x would have already been incremented to 3 and loop would have finished.Now since x is var scoped even when trying to print x within the loop would still print 3 as somewhere in the function x has been incremented and remember when i said when any changes done to var would be reflected throughout the function ? That's exactly why 3 is logged three times instead of 0..1..2.Hope that makes sense of why var being function scoped has this side effect on for loop.

Now we can still make the above code execute the way we want it to. The problem here is that x is function scoped but what if i change the for loop to this

for(let x=0;x<3;x++) {
setTimeOut(()=>console.log(x),1000);
}

In this case The above loop would print out

0
1
2

but Why ? Well let's take a look at the scope of let here

let :-
declaring a variable as 'let'

1.) Makes it block scoped which is
{
let i=0;
}
console.log(i);

the above code would throw an error since i would not be a part of the scope where console.log(); was executed.
2.) Unlike var let cannot be declared within it's same scope

we could do this

function () {
var i=0:
var i=1;

}
and it would work fine where the value of i is reassigned however
{
let i=1;
let i=2;
}
would throw 'i is already defined error'

Now since we have understood how let differs from var let's examine what a for loop exactly does trust me there is a lot happening than we know

/** Read this twice
Now since the console.log statements print out 0..1..2 even if the value of x has been incremented to 3.So we know that the place where the setTimeout callback function executes, the value of x within that block scope is the same as when it was called right ? .
**/
Here's how the for loop executes our let declaration,
1.) The for loop creates three separate lexical scope(block scope) for each iteration.
2.) the for loop copies the value from one lexical scope to the other lexical scope every time another iteration happens .

to explain the above two statements better let's break the code to it's simplest terms
The three iterations of the above for loop is like this

First Iteration :-

{
let x=0;
if(your condition) {
setTimeOut(()=>console.log(x),1000);
}
}
And x would increment and now the value of x is copied to the next lexical scope for the second iteration which would be

Second Iteration :-

{
let x=1;
if(your condition) {
setTimeOut(()=>console.log(x),1000);
}
}
again the post-increment operator for x would execute and finally

Third Iteration :-

{
let x=2;
if(your condition) {
setTimeOut(()=>console.log(x),1000);
}
}

And 'walaah' we got the answer to why it prints out 0..1..2 instead of 3..3..3 as opposed to var .But still the question remains that under which lexical scope the post-increment operator is executed .... haha We totally forgot about that right ?.

Whether the post-increment operator is part of the above three lexical scope or is it executed in a separate lexical scope ? Well the answer is this

/** Read this twice
"The expression we write(in this case the post-increment operator) is executed at the start of every lexical scope except the first one! ".
**/

Wait .... I know what you are thinking a for-loop would be identical in every iteration except the value of the variable right ? well not so and i can prove that javascript runs for-loop this way with a simple example,so much behind a simple for-loop right ? haha let's dig in !

The first iteration printed 0 and not 1,so from this we are pretty sure the expression is not part of the first iteration But wait ? Why couldn't the expression be at the bottom of every lexical scope and not at the top .Well that's exactly what i thought but think about it the x is block scoped right
so
{
let i=0;
setTimeOut(()=>console.log(i),1000);
i++;
}

if the above code were to happen... as we know that the setTimeOut function is a closure here, so when the setTimeout Function executes the value of 'i' would be 1 by that time and we should have gotten 1 but we got 0 and that's exactly why it's not a part of the first iteration .

So adding expression execution to the three iterations it would be

First Iteration :-

{
let x=0;
if(your condition) {
setTimeOut(()=>console.log(x),1000);
}
}

x is copied from the first lexical scope to the second lexical scope

Second Iteration :-

{
let x=0;

/** Expression **/
x++;

if(your condition) {
setTimeOut(()=>console.log(x),1000);
}

}

similarly x is copied from the second lexical scope to the third lexical scope

Third Iteration :-

{
let x=1;

/** Expression **/
x++;

if(your condition) {
setTimeOut(()=>console.log(x),1000);
}

}

And "tadaaa" We are now completely aware of where every piece of the for-loop executes and under which scope.Give yourself some credit here if you have followed all along from the beginning .Now let me change the above code for the final test to the let scope concept

for(let x=0;x<3;x++) {

setTimeOut(()=>console.log(x),1000);
x++;
}

What would be the output ? it would be...

1
3

If you have got this right you have mastered javascript for-loop execution ! :) But still let me explain what happens behind the scenes here

Our own incrementation as part of every iteration would look like this

First Iteration :-

{
let x=0;
if(x<3) {
setTimeOut(()=>console.log(x),1000);
x++;
}
}

now x is 1 in the above lexical scope so
x is copied from the first lexical scope to the second lexical scope

Second Iteration :-

{
let x=1;

/** Expression **/
x++;

if(x<3) {
setTimeOut(()=>console.log(x),1000);
x++;
}

}
x would be three now
similarly x is copied from the second lexical scope to the third lexical scope

Third Iteration :-

{
let x=3;

/** Expression **/
x++;

/** the if statement here would return false **/
if(x<3) {
setTimeOut(()=>console.log(x),1000);
}

}

Now it makes sense right ? as to why it printed out 1..3 where our own incrementation had a major side effect .To those who have been reading this post to the very end here's is something that is not part of the video Which is "what would happen if i were to write this piece of code "

for(let x=0;x<3;x++) {
setTimeout(()=>console.log(i),1000);
let x=2;
x+=2:
}

Now One of the two scenarios would likely be true
Either

1.) It should throw an error because we are declaring "let" inside the same block scope and since we know let cannot be re-declared within the same scope it should show "x is already defined" error.

2.) Or if the "let" declaration is moved to the start of the lexical scope then the for loop becomes a infinite loop as every time it comes within the for loop we are re-assigning the value of x .

The above two statements are the most rational ones but javascript has surprised us even here
The above loop would execute three times without any error where our "let" declaration has no side effect at all except for the logging part where it would log

4
4
4

in all the three iterations.Sounds confusing ? here's what is happening under the wood

whatever we write inside the for loop is executed inside the if block.The three iterations would look like this

First Iteration :-

{
let x=0;

if(x<3) {
setTimeOut(()=>console.log(x),1000); --> would console log 4 as x becomes 4 by

the time the callback function is
called .
let x=2;
x+=2;
}
---------------> this is where we have to pay attention if the x wasn't declared as let inside the if block the x inside the if block would refer the x declared outside of the if block.But here the x is re-declared under a new block scope which is the if block and therefore whatever operation we do to that variable has no effect on the variable outside the if block and therefore the for loop just runs as usual three times .

}

Second Iteration :-

{
let x=0;

/** Expression **/
x++;

if(x<3) {
setTimeOut(()=>console.log(x),1000);
let x=2;
x+=2;
}

}

Third Iteration :-

{
let x=1;

/** Expression **/
x++;

if(x<3) {
setTimeOut(()=>console.log(x),1000);
let x=2;
x+=2;
}
}

that is why the for loop executes 3 times although we get logged

4
4
4

in all the three iterations .

Yes finally we have finished understanding the javascript for loop completely! Yipee !!!! wait.. There is still more

Just a "wait what Meme"

Just one last piece to this whole puzzle,Which is "what scope is the assignment operator a part of " the first lexical scope ? Well no.... here's what's happening

let's try this for loop

for(
let x=(setTimeOut(()=>console.log(x)),0);
x<3;
x++) {

setTimeOut(()=>console.log(x),1000));
x++;
}

first let's clear up the assignment operator as it might seem confusing.All i am doing is just adding a closure to the assignment operation to find out the scope of the assignment operation that is all and assigning x to be 0;

Now if we thought the first iteration or the first lexical scope had the assignment operation

First Iteration :-
{
/** our assignment operation **/
let x=(setTimeOut(()=>console.log(x)),0); --- > our assignment statement as part

of the for loop which would not
be a part of the second and third
lexical scope or iteration
if(x<3) {
setTimeOut(()=>console.log(x),1000));
x++;
}

}

wrong but rational output :-

the output should be
1 --> this corresponds to the console.log from the assingment operation

1
--> the remaining two statements correspond to the body of the for loop
3

But this is not what is happening what it would actually log is

Right output :-

0 --> this corresponds to the console.log from the assingment operation

1
--> the remaining two statements correspond to the body of the for loop
3

Now it's clear even for the assignment operation the for loop creates a new lexical scope just for the assignment operation .Yes javascript does that and that scope is different from the all the other lexical scopes as part of each iteration .

well Now you can celebrate you have understood the for loop in javascript completely and this can help you to understand how javascript on the whole works in general and how closures play a role along with it .

Just a "it's finally over meme"

Give this post another read and do comment if i haven't explained a few parts well enough . I do apologize if things weren't as neatly expressed as it should have since this is my first post i am writing.Hope you all learnt something and i didn't waste your time .Until next time See ya !

Top comments (1)

Collapse
 
srganurag profile image
Anurag Swain

really helpful, thanks for the info!