Recently, I had been given a mock interview by someone in the industry for a longtime. In this interview, I was posed a question about javascript's setTimeout function. I was given a setTimeout function inside of a for loop, as shown here:
for (var i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i);
}, 0);
}
//What is the expected output?
Here is MDN's definition of setTimeout: The setTimeout() method of the WindowOrWorkerGlobalScope mixin (and successor to Window.setTimeout()) sets a timer which executes a function or specified piece of code once the timer expires.
So we pass in a callback function to be executed after the timer is finished. If you haven't already noticed, the setTimeout function has a delay of 0 milliseconds. Think about the results before looking at the expected output.
for (var i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i);
}, 0);
}
//expected output:
//5
//5
//5
//5
//5
Are you surprised?
I sure was!
Here's why it didn't print 0,1,2,3,4: setTimeout, while having a timer as a delay, still schedules the callback to run asynchronously.This means that even though the timer argument is 0, it still waits for the for loop to finish before invoking each callback.
So now the question is, why all 5's? Shouldn't it have received the variable in each loop? Because of closures in javascript, the callback function only had access to the i variable after the for loop was finished, which was 5. This is in part due to the var in the for loop, which I personally detest. The scope of the variable was function scoped.
How do we fix this?
Solution 1
for (let i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i);
}, 0);
}
//expected output:
//0
//1
//2
//3
//4
The let keyword allows the variable to be block scoped.We change the var to let inside of the for loop in order to create a separate scope in each loop, giving the setTimeout callback function access to each local i value in their respective closure.
Solution 2
let log = (x) => {
setTimeout(() => {
console.log(x);
}, 0);
}
for (var i = 0; i < 5; i++) {
log(i)
}
//expected output
//0
//1
//2
//3
//4
In this solution, we pass the variable into a defined function and immediately invoke it.
Top comments (2)
You can also go for the IIFE solution:
for(var i=0; i<5; i++){
(function(i){
setTimeout(() => {
console.log(i)
},0)
})(i)
}
Nice one!