DEV Community

Levi Meahan
Levi Meahan

Posted on

Closures/Scope and the setTimeout for loop question

I've researched the event loop in JavaScript before, and have a decent general understanding of how it works, but I was recently caught off guard by this classic interview question.

// What does the below code output?

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

// Answer: 4 4 4 4

I've seen this around and remembered that it's a trick question, and there are some ways to solve it like using let i = 0; instead of var i = 0; but I didn't fundamentally understand why that worked. (If you want a great, very detailed explanation of this question, check out this post: https://medium.freecodecamp.org/thrown-for-a-loop-understanding-for-loops-and-timeouts-in-javascript-558d8255d8a4)

But there was still one thing I didn't get. The callback gets put into the event queue, so certainly it has to keep a copy of i in order to use it, right? Since our main code is going to finish running before the callback gets called, shouldn't the variable i not exist anymore? I didn't see an explanation, so I went to go review MDN's article on Closures.

The article explains - Closures (aka the callback we pass to setTimeout) keep a reference to the environment/scope they were created in, including references to its variables, even after that environment/scope stops running. Ohhhh. Suddenly this makes a lot more sense. Even after our main code finishes, a reference to its variables(at a minimum, the ones the closure uses) is kept around for the closure to access. So if the i used in our callback is a global variable within that environment, the closure will use that reference.

Thus, the many solutions to this question revolve around creating a different scope for i to exist in each time we call setTimeout, so that each callback in the event queue maintains a reference to a completely different variable, and we really have 4 different variables all named i, in different scopes.

Which actually raises an interesting, not completely obvious behavior of the following solution:

for(let i = 0; i < 4; i++) {
   setTimeout(function() {
      console.log(i);
   }, i * 1000);
}

// 0 1 2 3

For this to work, we know that i needs to actually be a different variable each time we call setTimeout. Which means that when we use let in a for loop like this, it's actually creating a new variable named i on every iteration of the loop. Never really thought about it like that!

And this also raises a somewhat important bit of performance knowledge to keep in mind - if we maintain a reference to a closure, we're maintaining a reference to the entire scope it was defined in (even with possible compiler optimization, it's at least the variables used within the closure).

Further reading that explains much more about closures and scope:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let

https://github.com/getify/You-Dont-Know-JS/tree/master/scope%20%26%20closures

Top comments (4)

Collapse
 
dmorrison profile image
Derek Morrison

Which means that when we use let in a for loop like this, it's actually creating a new variable named i on every iteration of the loop.

Is this right? Why do you get an error if you use const then?

Collapse
 
mertasilturk profile image
Mertasilturk

you can't change the value of variable created with const .

Collapse
 
dmorrison profile image
Derek Morrison

Right, I understand that. My question was more about let variables and how they work with a closure. It seems like, when a closure is created that references a variable created using let, that the value is saved statically. This is opposed to how it works with a variable declared with var.

Thread Thread
 
mertasilturk profile image
Mertasilturk

yes thats right. with let you have i = 0 i =1 i = 2 i= 3 saved in memory . because of block scope. with var you only have i=3 . because of function scope you can change outside of block so values of "var i "not preserved .