Help understanding mechanics of this closure

I’m trying my hardest to get a REAL understanding of closures, not just ‘accepting’ examples at face value. I ran into the following example and have partially understood it, but some questions about mechanics remain. I wonder if someone might help me understand?

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

foo();

What I understand:

  • Instead of ‘expected’ result of 0, 1, 2. as we would get declaring i with let, we get 3, 3, 3.
  • This happens because var is functionally/globally scoped vs let’s block scope
  • with var the value of i is read when the loop is complete, so it reads as 3.

What I don’t understand:

  • Why is i not read a stage earlier, what causes the delay?
  • If i is read as 3, is the loop not over and if the loop is over, why does it start again, giving us 3 logged 3 times instead of only once.
  • Does the loop actually run 9 times instead of 3?

Any help with the mechanics, what is happening under the hood would be greatly appreciated.

Thanks!

Hi @bilsner

See what happens if you add another console:

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

foo();
// 0 no delay
// 1 no delay
// 2 no delay
// 3 no delay
// 3 1s delay
// 3 2s delay

Outside the setTimeout you have the values 0, 1, 2 as i. That is applied for the calculation of the delay:
1st round: 1000 * 0 // no delay
2nd round: 1000 * 1 // 1s delay
3rd round: 1000 * 2 // 2s delay

Yea, I’m still confused, I get that because var is function scoped it can (and does in this case) evaluate i outside of the setTimeout code block, I just don’t understand why it has to.

The other bit I really don’t get is this. If i is evaluated to 3, that means the loop has been completed, it’s run 3 times, so i is 3. So shouldn’t that mean the outer function is complete and the log would show a single 3?

It seems like, somehow this arrangement causes the loop to re-start and run 3 times, so 9 loops in total. This is very confusing.

EDIT: Sorry, I realised I used the word delay so you probably thought I meant the setTimeout, what I mean was why is i not evaluated inside the block, why is it only evaluated a step later. Sorry for the confusion

Ok, then I don’t fully understand your question, indeed.

I am pretty sure though that the loop runs three times, not nine times. Whether you run it with i declared as var or as let.
In your example with the console only inside the setTimeout, you get three values logged to the console. One per loop.
When you add a console outside of the setTimeout as I did, you get six values logged to the console. Two per loop.

The loop starts at 0, so when it ran three times, i is 2, not 3. So I don’t understand why it logs 3, actually. Take this example, which should more or less do the same as your function:

const delayed = i => {
  setTimeout( () => { console.log(i) }, 1000 * i, i )
}
const f = async() => {
  for (var i = 0; i < 3; i++) {
  	console.log(i);
    var val = await delayed(i)
  }
}
f();
// 0 no delay
// 1 no delay
// 2 no delay
// 0 no delay
// 1 1s delay
// 2 2s delay

Declared with var as well. Same result as if run with let.
So maybe someone else can enlighten us why your versions logs 3.

1 Like

Thankyou.

Here I made a codepen with the function logging 3, 3 times to the console if anyone wants to look at it. It’s pretty weird :grinning:

1 Like

Have you tried looking at the MDN article on closures? Closures - JavaScript | MDN

A side note on this: for me closures are strangely named. I think this wiki article has a good blurb on the etymology Closure (computer programming) - Wikipedia and it should be contrasted (so as not to mix up) with mathematical closure (easy example, the set natural numbers are closed under addition, since natural + natural will always yield natural, which is within the same set). It’s not always possible, but for me connecting words etymologically helps me relate to them and retain them better. And the naming of the term closure is one of those things in js that has always irked me just a bit. /endrant

1 Like

Yeah, I have read the MDN. What I have understood about the subject, it mostly came from there. I think I get lexical scoping, taking the first example in the article…

function init() {
  var name = 'Mozilla'; // name is a local variable created by init
  function displayName() { // displayName() is the inner function, a closure
    alert(name); // use variable declared in the parent function
  }
  displayName();
}
init();

I understand that displayName will be evaluated before the init function is called. This is due to lexical scoping evaluating the internal variables pre-emptively (please correct me if I’m wrong)

My confusion is how in the example I gave, the variable i is evaluated as though the function was completed, before the function was completed and then that process repeats for each loop in the function

Is that how lexical scoping works?

EDIT: btw, although this is messing with my head, it’s a fascinating subject

Hi,

You’re question is interesting indeed. (Maybe you have an interest in lower-level programming?)

Try running your code here to see how it’s being interpreted (I think it should clarify your evaluation questions… and potentially begin another flurry of tangential questions, which is good :slight_smile: ): https://www.jsv9000.app/
Also, maybe try different examples related around this theme and that might give also give a fuller picture.

2 Likes

I just ran the code, it told me of for calling functions in a loop haha. My first takeaway is that I clearly need to learn about the call stack for it to make any sense, so I’m going to go and look into that now.

Thanks for the replies, I’ll be back when I’ve got more of a clue

1 Like

Could you go a little into details as to why @bilsner 's function logs 3 although the loop stops at i===2 and the setTimeout does not increment the value any further, please? In case you answered it in this post or the answer hides somewhere in the MDN article about closures, I did not get it.

I’m only guessing since I don’t know the low-level details of js. Basically running foo sets the three anonymous function calls on the task queue (because of setTimeout). Because foo is in the call stack, it has to fully “run” foo before evaluating the anonymous functions. Of course, by the end of the loop, i is now 3, so when it’s time to evaluate the 3 functions, they will log 3. (Note, you can change the setTimeout to the quickest setting and this will still be the case). However, I think this is because of how setTimeout works and has nothing to do with the fact that anonymous functions are involved.

Check out the visualizer: https://www.jsv9000.app/

Run his original code and compare it to how this runs to this (to clarify that the functions being anonymous doesn’t affect this particular situation):

function foo() {
  for (var i = 0; i < 3; i++) {
    let x = function() {
    	console.log(i);
  	};
	x();
  }
}
2 Likes

So from what I understand, this might not be the best example to demonstrate closures, it just muddies the waters as it’s the setTimeout causing the odd behaviour…

…and so the init function with nested displayName() is actually better as it shows the way variables are evaluated before the outer function is called?

Edit, as just playing with the foo() on chrome debugger and (tho I’m not super familiar with it) watching the call stack it looks like the loop only runs 3 times, but i is evaluated as 3 each time, so maybe something to do with setTimeout

Yes, not so much odd, but definitely more complicated:

Just take

function baz(){
    setTimeout(function(){
         console.log("foo")}, 100);
    console.log("bar"); 
}

baz()
//prints
//bar
//foo

In a way this is good to review setTimeout.

Your intuition of looking into call-stack is also correct @bilsner, but it is a bit of a rabbit hole at first. If you’re interested, see if you can check out Computer Organization and Design by Patterson. It might seem scary at first but I think it helps de-mystify the code.

@bilsner also to be technical, the loop is evaluated as something to be passed on to the task queue, meaning it doesn’t evaluate the inner expression until the whole function is done. (again because of setTimeout)

1 Like

Thank you, that’s a helpful site.

Ok, that makes sense. So as I understand it now (still a bit vague):
The function foo is sent to the stack and runs through, logging everything from the console in the first level. It also sends the timeout function to the queue with the current values of i (0, 1, 2). That’s why the version declared with let logs 0, 1, 2 twice. When the functions from the queue are executed, the original values are overwritten by the value of i from the global scope by the time of execution in the var version.

1 Like

Heh, I’ve fallen down a giant rabbit hole

Factory functions return a closure.

const logFactory = base => {
  return x => Math.log(x) / Math.log(base)
}
const logBase2 = logFactory(2)
const logBase8 = logFactory(8)
const logBase16 = logFactory(16)
console.log(logBase2(64))
console.log(logBase8(64))
console.log(logBase16(256))
6
2
2

x is the free variable, if I’m not mistaken. Anybody, step in and rescue me if it should be base. To my mind base is locked in to (bound by) the environment.

2 Likes

I have the explanation to the foo() setTimeout behaviour. First a more simplified version to show what setTimeout is doing, and it is a good example of closure, contrary to what I thought earlier.

function foo() {
   var i = 1;
   setTimeout(function () {
     console.log(i);
  }, 3000);
    console.log('surprise!');
 }

foo();

Here ‘surprise!’ is logged instantly (please excuse me if you already know this, I expected it to wait for i)
The salient point is setTimeout isn’t making the call stack wait, it creates a reference for i and then moves on, so logs ‘surprise!’ first and when the timer runs out, recalls the reference to i and logs that.

This is why the original foo() function prints 3, 3, 3 and not 0, 1, 2.

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

foo();

Here, setTimeout again forms a closure including the reference to i, but then setTimeout waits a second before logging i. This is the key point. While setTimeout has been storing a reference to i, the loop has already run to completion because setTimeout already did it’s job (as far as the foo() call stack is concerned). So when it logs i after 1 second, 2 seconds etc. It will use it’s reference of i, not a stored value of i and i will be 3, all 3 times because the loops finished already.

Please excuse the rambling explanation, but that is it as far as I understand it. Isn’t javascript amazing?

EDIT. Credit for this explanation goes to Namaste Javascript YouTube channel, it’s a really good channel

EDIT2. @toastedpittabread I now realised this is what you were explaining to me a couple of posts back, I just wasn’t quite ready to understand it at that point, so thankyou all and Namaste Javascript for getting me there!

Hey there,
thanks for the update.

This still doesn’t fully explain to me why the logging function in the setTimeout function logs 0, 1, 2 when declared with let and 3, 3, 3 when declared with var, since the order of execution is the same in both cases. The anonymous functions with the reference to i are sent to the task queue while foo is still in the call stack.
It also does not explain why my example with the setTimeout defined outside of foo as a callback logs 0 1 2 when i is declared as var. Unfortunately, I cannot run my example in the visualizer provided by @toastedpitabread .
That’s why I assumed that i in the closure is overwritten by the current value of i later on when declared with var as var is available in the global scope. But probably it’s rather about where the reference is stored. Maybe that’s what @mtf was referring to when talking about the free variable.

1 Like

When declared with let the scope is block so i is evaluated here…

function foo() {
  for (var i = 0; i < 3; i++) {
    setTimeout(function () {
      console.log(i);
    }, 1000 * i);
   //** i evaluated here with let (0, 1, 2)
  }
}

Whereas with var is evaluated here.

function foo() {
  for (var i = 0; i < 3; i++) {
    setTimeout(function () {
      console.log(i);
    }, 1000 * i);
  }
 //** i evaluated here with var (3, 3, 3)
}

I get what you mean though, I understand why var is 3, 3, 3, the delay in evaluation causes it, now I’m a bit sketchy on let. If let acted the same way as var, just with block scope wouldn’t we expect undefined? I wonder if there’s something different going on in how the lexical scoping handles it, like you said, a difference in the way i is referenced?

It’s intriguing for sure.