Scope and closures are an integral part of JavaScript. If you don’t understand them thoroughly, then many parts of the language–such as the combination of a for loop and setTimeout()–can seem mysterious.

I’ve written separately on how scope impacts closures and hoisting in JavaScript. In this article we will go in-depth on the specific case of setTimeout() inside a for loop, explain why it occurs, and discuss several fixes.

For loops

The basic for loop is a staple of JavaScript development. It will run until something in it is false; its natural state is to go on forever! A for loop consists of three clauses: an initialization expression, a conditional expression, and an update expression.

for (var i = 1; i < 5; i++) {
  console.log(i);  // 1 2 3 4
}

Here our clauses are as follows:

  • Initialization: var i = 1
  • Conditional: i < 5
  • Update: i++

But note that at the end of our for loop the variable i actually has the value 5, not 4. For each loop we start with our initialization value, increment it by 1, and then check against the conditional expression. In other words, we execute clauses in first, third, second order even though it’s logical to think they’d be executed first, second, third.

Let’s review what actually happens in our for loop:

  • 1st pass: i is 1, increment to 2, check is 2 < 5? Yes, so output.
  • 2nd pass: i is 2, increment to 3, check is 3 < 5? Yes, so output.
  • 3rd pass: i is 3, increment to 4, check is 4 < 5? Yes, so output.
  • 4th pass: i is 4, increment to 5, check is 5 < 5? No, so exit loop.

Hopefully it’s clear now why i is equal 5 even though we only output 1-4. We can prove this with the following code:

for (var i = 1; i < 5; i++) {
  console.log(i); // 1 2 3 4
}

console.log("The value of i is now: ", i); // "The value of i is now: 5"

Note as well that since var is function-scoped, i will be set to the closest enclosing function. In this example, that would be the global scope. More on this fact shortly.

setTimeout

What if we want a one second pause before outputting each number in our loop? It seems logical we could just add a setTimeout method to achieve this.

for (var i = 1; i < 5; i++) {
    setTimeout(() => console.log(i), 1000)  // 5 5 5 5
}

Why isn’t the output 1 2 3 4? The short answer is that the for loop executes first, then it looks for the i value, which is 5, and then outputs four times, one for each loop iteration.

Even if we use a setTimeout of 0, the result is still the same.

for (var i = 1; i < 5; i++) {
    setTimeout(() => console.log(i), 0)  // 5 5 5 5
}

The reason is that setTimeout is really an event that happens later, as in after our for loop has completed. So it doesn’t matter what time we set, even no time, setTimeout will execute after and when it does it will look for the value of i.

Closure

The setTimeout function can access the value of i because of closure. We ask for i within our console.log statement, but its value is set in an outer, enclosing scope, that of the for loop. Since inner functions have access to variables in an outer function, we are able to go up a level and retrive the value of i, which is 5.

Event loop

The deeper explanation of what’s happening however relates to the Event Loop. The JavaScript code we wrote above is asynchronous: one piece of code ran now and then another later. But JavaScript itself is a single-threaded language. It can only execute one piece of a code at a time. So how can JavaScript execute asynchronous code?

The answer is that the JavaScript engine does not run on its own! It is always run within a hosted environment: usually a web browser or NodeJS on a server. What’s in this hosted environment? There are two main things to focus on:

  • Web APIs
  • an event loop

The setTimeout function is not actually a part of JavaScript. Instead it is a Web API provided to us by the web browser. In other words, while it is running, the browser is controlling its execution, not our main JavaScript engine.

The browser also has an event loop, a mechanism for executing code over time. When we call setTimeout with a callback function, we send that request over to a Web API to execute. Our JavaScript engine continues along, for example finishing the for loop in our code example. Then once setTimeout has finished waiting – whether it was 0 seconds or 1,000 – the event loop notices that it is done and feeds this code back into our main JavaScript engine.

The best explanation I know of the event loop comes from this 30 minute talk. It’s fantastic.