loading...
Cover image for 3 topics in 1 JavaScript interview question

3 topics in 1 JavaScript interview question

maciekchmura profile image Maciek Chmura Originally published at maciek.cloud Updated on ・5 min read

Programming interviews are hard. Live coding during the interview is even harder.
I have a feeling that as soon as I have to type code in front of another person myIntelligence -= 10;
At my current company, lead developers regularly conduct interviews with potential new candidates. There are prepared some sets of questions for JavaScript developers, but there is this one questions that almost always gets asked:

    // what will be logged in the console
    // and how to fix it to log 0, 1, 2??
    for (var i = 0; i < 3; i++) {
      setTimeout(function() {
        console.log(i);
      }, 1000);
    }

It's a pretty typical question.
What is so special about it?
Well, in my opinion, these 5 lines touch 3 interesting aspects of JavaScript.

  • var, let and const
  • closures
  • event loop

Let's break it down to see what is happening here.

var let const

ES6 introduced new variable assignment keywords: const and let. You probably already know that they are block scoped and var is function scoped.
Some simple cases to illustrate this behavior.

    // simplest JavaScript example in the world:
    {
      var name = 'maciek';
      let surname = 'chmura';
    }
    console.log(name); // -> maciek
    console.log(surname); // -> surname is not defined

    // what's happening under the hood:
    var name;
    {
      let surname;
      name = 'maciek';
      surname = 'chmura';
    }
    console.log(name);
    console.log(surname);

OK, let’s put this in context of a for loop.

    for (var i = 0; i < 3; i++) {
      console.log(i); // -> 0 1 2
    }

    // what will happen when var is changed to let?
    for (let j = 0; j < 3; j++) {
      console.log(j); // -> 0 1 2
    }

Both loops generate right output. But in some slightly different way. var 'jumps' to global scope and let 'jumps' into the loop, and gets initialized on each iteration.
It can be illustrated like this:

    // var lives here
    for (var i = 0; i < 3; i++) {
      console.log(i); // -> 0 1 2
    }
    console.log(i); // -> 3

    for (let j = 0; j < 3; j++) {
      // let is available only from here
      console.log(j); // -> 0 1 2
    }
    console.log(j); // ReferenceError: j is not defined

OK, easy peasy... This is how block scoping works... moving on.

Closures

The mystical land of JavaScript Closure.
What is the raw definition of a closure?
Lets check MDN

A closure is the combination of a function and the lexical environment within which that function was declared.

Please take a deeper look at this article from MDN. Very smart people contribute to this knowledge base, let's trust them :)

  • What exactly is this lexical environment?
  • Does it go away at some point?
  • Who and when decides about it?
  • How can I control it?

For a long time, I could not wrap my head around it.
It was until I added 2 visual aids to help me understand it.

  1. 🎒 A Backpack. I like to think of closures as backpacks of functions. When a function is defined it adds to its backpack all the values it might need in the future.
  2. 🚚 A Garbage Collector. A truck that removes old code. Unlike in C language, you don't have to do malloc() and free(), it will be handled automatically.

When some function has executed and returned a value, we can safely remove this function definition from memory 🚚🗑. The same thing goes for values that are not reachable anymore.
Things get interesting when a function returns a function.
I don't want to reinvent new examples and definitions, so I will just add some layer of visual helpers.
MDN example (with line numbers):

    function makeFunc() {          // 1
      var name = 'Mozilla';        // 2
      function displayName() {     // 3
        alert(name);               // 4
      }                            // 5
      return displayName;          // 6
    }                              // 7
                                   // 8
    var myFunc = makeFunc();       // 9
    myFunc();                      // 10

Let's imagine a simplified JavaScript interpreter workflow. What JavaScript runtime is ’thinking’ while running code.

  • (line 1)makeFunc function definition, moving on.
  • (9)Declare myFunc variable and assign to it the result of running makeFunc, execute makeFunc
  • (1)Jumping into makeFunc definition.
  • (2)Ok, a variable name with value Mozilla.
  • (3)displayName function definition, moving on.
  • (4)return displayName function definition

First plot twist. Whole function definition is returned here. There are no () at the end of displayName.
Second plot twist. A closure is observed. Where? displayName puts into its 🎒 var name (it is within lexical scope of displayName)

screen

makeFunc executed and returned the whole function definition of displayName with its closure (a 🎒) holding a reference to a value in name.
The garbage collector can't delete lines 1 to 7 from memory because sometime in the future myFunc might get executed, and then displayName with its closure will be needed.

  • (10) execute myFunc

This is how I understand closures.
Now I can see it!

panda

Let's move to the last part of the puzzle.

Event Loop

There is no better way to learn about event loop than from amazing Philip Roberts talk at JSConf EU.
Just watch it...

🤯 mind blowing right?
OK! Finally, with all the knowledge, let's break down what is happening in the interview question.

    for (var i = 0; i < 3; i++) {
      setTimeout(function() {
        console.log(i);
      }, 1000);
    }

With each iteration of the loop, setTimeout sends function with console.log(i) to the web APIs and start the countdown.
In the meantime, we will continue with the loop. Another console.log(i) will be pushed to web APIs and so on...
Loop finished execution. Call Stack is empty.
In the web APIs, after 1 second console.log(i) gets pushed to the Callback Queue. And another one, and another one.
Because the Call Stack is empty, Callback Queue can push its first element to the Call Stack to execute it.
So the first console.log(i) executes.
It looks for an i.
What is the value of i?
It's 3. From the global scope.
Why?
Loop finished its iteration and updated the i to 3 at the end.
var i is function scoped (for loop is not a function), and was hoisted outside of the loop to the global scope.
Call Stack is empty again.
Second console.log(i) moves to the Call Stack.
What is the value of i? It's 3 again. It's the same value.

get_it

How to fix it to log 0, 1, 2?
One way of fixing it is by changing var to let.
Now while looping, each i is initialized and assigned a value of current iteration and put into closure (a 🎒) of function that will log it.
After 1 second, when the Call Stack is empty, Callback Queue will push function with console.log(i) and it's closed over value of i back to Call Stack and execute it.
0, 1, 2 will get logged respectively.
Done.

Next question please.

Now, when you know what is happening exactly, what else can be done to fix it?

Disclaimer:
I wrote this mainly for myself to study these topics. If anything here is wrong, please point it out in the comments so we all can learn :)

Discussion

pic
Editor guide
Collapse
laurieontech profile image
Laurie

Really well written! One minor thing, based on that loop, I believe it will log 0, 1, 2 and not 1, 2, 3? That detail doesn't effect all the wonderfully explained concepts though!

Collapse
maciekchmura profile image
Maciek Chmura Author

ups :) you are absolutely right, thank you for pointing this out :)

fixed

Collapse
benjaminadk profile image
benjaminadk

I believe before the addition of let one way to solve this would be to wrap the call to setTimeout in an IIFE. I guess by immediately invoking the outer function with the current value of i it seals the value at that moment vs getting it from the global scope when the inner function is called.

  for (var i = 0; i < 3; i++) {
    (function(i) {
      setTimeout(function() {
        console.log(i)
      }, 1000)
    })(i)
  }

// 0 1 2

Collapse
josefaidt profile image
josef aidt

Maciek this is great! The final recap really tied it together, and now I understand the logic behind this question much more thoroughly. Excellent stuff, can't wait for the next one.

Collapse
maciekchmura profile image
Maciek Chmura Author

I'm blushing, thank you ;)

Collapse
slatham profile image
Steve Latham

The backpack analogy is a good one. I often think the names given to these concepts make them harder to learn. Closures to me would indicate some kind of ending or return. I assume it is something to do with being enclosed, maybe? Similarly the "rest parameter" sounds like it's going to have a little pause before entering your function. I had a less imaginative way to remember closures - savedScopeForLater! From now on though, Closure = backpack!

Great article, Maciek!

Collapse
rinatvaliullov profile image
4rontender

Yet a little another solution

for(var i = 0; i < 3; ++i) {
    setTimeout((function(z) {
        console.log(z)
    }).bind(null, i), 1000)
}

// 0
// 1
// 2
Collapse
joeberetta profile image
Joe Beretta

Thank you very much. In a days I will be interviewed and it's very helpful!!! 👌👌👌

Collapse
maciekchmura profile image
Maciek Chmura Author

Good luck on the interview, I hope you will get the job :D

Collapse
joeberetta profile image
Joe Beretta

Thanx) And I hope😊

Collapse
carlfoster profile image
Carl

Will this guarantee that it emits 0,1,2? Since setTimeout() is async, I don't think it guarantees execution order.

Collapse
vekzdran profile image
Vedran Mandić

This is pure essentials in JS. Good wrap up! Thanks for writing and sharing.