Wondering how the Closure ❤️ and setTimeout ⏳ in the For-loop Question can blow your mind?

Wondering how the Closure ❤️ and setTimeout ⏳ in the For-loop Question can blow your mind?

This article will cover the trickiest and the most asked question in interviews of how closure and setTimeout works in the for loop.

Are you ready for the blast my dear friends? Let's fall in love with the peculiarity of JavaScript once again.

When I was studying this Closures topic quite closely and had started developing the feeling of understanding it completely, I stumbled upon my balance as soon as I encountered a shenanigan code snippet and realized that JavaScript has never failed to surprise me.

Prerequisite: Before we begin, I expect that you are familiar with how the call stack, the task queue and the event loop work in JS giving it an asynchronous behavior.

Just a Simple Question 😌

It's kind of good, to begin with, the simple stuff like how we would print the numbers from 1 to 5 in a sequence.

for(var i = 1; i <= 5; i++) {
    console.log(i);
  }
console.log('The loop is done!');

It was pretty easy! Just iterate with a For loop.

Trickiest Interview Question 🤨

Now, what if I say that we have to print the numbers consecutively with a delay of one second? Now, we are the "COOL" 😎 developers who seem to believe: How cool and easy it would be to do it by just using the setTimeout() function.

for(var i = 1; i <= 5; i++) {
    setTimeout(function() {
       console.log(i);
    },1000);
  }
console.log('The loop is done!');

Do you expect your answer like this?

Cool Developer's cool way of answer 😎

// Output
1
// after one second
2
// after one second
3
// after one second
4
// after one second
5
The loop is done!

My dear friend, I only break the loop and don't want to break your heart but the output in real is something like the below:

Heart-breaking Output 😥

The loop is done!

// after one second, prints the numbers once and for all.

6
6
6
6
6

What did we Cool developers think? 🥲

Surprisingly, we get the output number 6 printed five times all at once after a second. How is that possible, why would JavaScript do that? Welcome to the world of JavaScript. 😁

The trickiest part starts from here 🤯

What's the problem? 🤔

for(var i = 1; i <= 5; i++) {
    setTimeout(function() {
       console.log(i);
    },1000);
  }
  console.log('The loop is done!');

You must be questioning "where was the problem"? The problem is lying around the variable i that we passed into the setTimeout() function and the setTimeout starts executing after the for loop ends. The setTimeout function is asynchronous and to understand how this works, we will print the console.log(i) statements before the setTimeout function.

for(var i = 1; i <= 5; i++) {
    console.log(`Loop running: i value is ${i}`)
    setTimeout(function() {
       console.log(i);
    },1000);
  }
console.log('The loop is done!');

In some points, we will break down this piece of code:

  • The JS engine starts running the For loop where we have declared and initialized a global variable named i equal to 1.

  • Each iteration of the loop sends the setTimeout() to a web API where the timer is attached to it for passing one second time.

  • Till now, the setTimeout() functions are still counting down but the loop is over and Loop running statements get printed. The only statement that is left in the call stack to be printed is The loop is done, and it also gets printed. Now the call stack is empty.

  • Since the call stack is empty, the setTimeout console.log statements are now ready to be executed and sent from the task queue to the call stack by the event loop.

  • But remember, by this time the global scope has only one value of the variable i, which is 6 because the loop is terminated when the value of i becomes greater than 5. Hence, we get the above output 6 printed five times.

  • You must be wondering now what if we change the timer seconds from one second to 0? In such a case, we can reach the expected output. But that is not true, because the setTimeout functions will be still handled by the web API. You can give it a try.

A Quick Revision of Scope 🤓

We need to jog our memory around the scope of var and let to dig into the solution to this trickiest question.

Scope of var

  • We know that a variable declared using the var keyword defines a variable globally, or locally to an entire function regardless of block scope which simply means that it is function scoped.
var username = "riya777";
function login(){
    var username = "riya727"
}
console.log(username)
  • What will be the output of the above code?

  • As expected, it will be "riya777". The username "riya727" will not overwrite "riya777" and why? because we know that var is function scoped and the JS engine will store the different references in its memory space for both variables technically.

Scope of let

  • A variable declared using the let keyword is limited in the scope of the block, statement, or expression in which it is used.
let username = "riya777";
if (true){
    let username = "riya727"
}
console.log(username)
  • The above code will print "riya777" since both the variables will have different references stored in the memory space. The username "riya727" is in the block scope which cannot be accessed by the console.log because let is block-scoped.

  • Let us tweak the above code now by replacing it with the "var" keyword.

var username = "riya777";
if (true){
    var username = "riya727"
}
console.log(username)
  • "riya727" will be the output printed now. It is known that the "var" is only function-scoped and here we have an if block statement in the code, in such a case, the username "riya777" will be overwritten by "riya727" because we haven't used here "let" block-scoped but "var" function-scoped only./

Unleashing: what's going on? 😼

  • The setTimeout console.log() statements can be executed only when the call stack is empty. If the call stack is empty that means the for loop has been ended. So, when the for loop was running, the value of i was getting incremented until the for loop ended and the value of i became 6 later.

      for(var i = 1; i <= 5; i++) {
          console.log(`Loop running: i value is ${i}`)
          setTimeout(function() {
             console.log(i);
          },1000);
        }
      console.log('The loop is done!');
    

Now if you observe that the JS engine stores a reference to the same i variable in its memory space and not the actual value at the moment inside each loop because the for loop statement is a block scope and i with the "var" keyword is not block-scope but function-scope only. This is why variable i is not the same variable and we see 6 getting printed all the time.

How to solve it? 🌟

We need to take the value of i and package it with the setTimeout() statement in its own bubble as a way to retain the actual value of i present while each loop is running. So, we can have separate copies of i stored as a reference in the JS engine's memory and get our expected output.

Are you also excited and happy about how we will solve this mind-bending question very soon?

let's do it then.

Ways to solve it 🫡

To address the issue, we need to pass the actual value of i at the moment of each loop iteration in the for-loop statement. It can be done in a few ways.

Using the "let" keyword

Instead of using "var", we will use the "let" keyword in the code snippet.

for(let i = 1; i <= 5; i++) {
    setTimeout(function() {
       console.log(i);
    },1000);
  }

In an instant, see, the output is in our favor. How?

  • Because the "let" keyword is block-scoped. By this, the JS engine makes i available only inside our for loop. So, even after the loop is terminated, the variable i doesn't get changed to 6 like used to happen using the "var" keyword accessing the global scope. Using "let" makes "i" accessible only inside the for loop and creates a new separate copy of the variable at every loop iteration storing the reference in memory.

Still using the "var" keyword

It can still be solved with the "var" keyword but of course, we know that "var" is not block scoped but we know that it is a function-scoped and remember, each function creates a unique scope. Just with this piece of information, we can do it.

for(var i = 1; i < 6; i++) {
    function wrapper(){ // creating a unique function (scope) each time
       var wrap = i; // saving i to the variable wrap 
       setTimeout(()=>{
          console.log(wrap);
       },1000);
    }
    wrapper();
 }
  • Since "var" is function-scoped, we create a wrapper function that will create a unique function scope in every loop iteration to preserve the value of i by storing it in a variable called wrap so that we have separate copies of wrap to be accessed properly by the console.log(wrap) statements even after the loop is ended. It will be a way of remembering the value of i in wrap with the help of Closures.

Using anonymous function

We can use the anonymous function also as a wrapper to accomplish the output.

for (var i = 1; i <= 5; i++) {
  setTimeout(
    (function (wrap) {
      return function () {
        console.log(wrap);
      };
    })(i),
    1000 
  ); 
}
  • We wrap the inner anonymous function in another one. In the outer anonymous function, we take a parameter wrap and pass the wrap into console.log(). During each loop iteration, we pass the value of i into the outer function which returns the inner anonymous function to be used for setTimeout() with the value of i captured in its preserved variable wrap. This way, we will get the intended output.

Using IIFE

IIFE is known as an immediately invoked function expression.

for (var i = 1; i <= 5; i++) {
    ((i) => {
        setTimeout(() => {
            console.log(i);
        })
    })(i);
  }
  • IIFE will create a new unique scope for the setTimeout function in each iteration to retain the value of i for console.log() statements.

Isn't it awesome 😁that we found many alternatives to get the expected output? We should definitely know all these apart from the solution using "let", it would be helpful for the interviews and in-depth understanding of JS.


We did it 🤩.

I hope this article was helpful to you in some way, understanding that small yet shenanigan code snippet like a kid and making you feel happier to do exceedingly well when the interviewer asks you this question to trick you into the notorious concept of Closures with setTimeOut in For Loop.

Until then Happy coding and Love JavaScript 😍. Stay tuned😉 for the next article.