Why setTimeout Doesn't Work in Loops: Results Coming Together After Timeout

You're trying to make "Hello" appear after a 5-second delay, and you want it to happen 10 times. You created a function called printHello with a 5-second delay using setTimeout, and then you ran this function 10 times in a loop.

Here's the code:

function printHello() {
  setTimeout(function () {
    console.log("Hello");
  }, 5000);
}

for (let i = 0; i < 10; i++) {
  printHello();
}

However, you notice that all the "Hello" messages are printed together after 5 seconds.

The question is, why doesn't setTimeout work as expected in a loop?

Loops operate synchronously, meaning they go through all their iterations effectively "at once." In this case, each printHello() in the loop is getting called simultaneously, one after the other. Consequently, each function starts its 5-second timer at the same time, leading to all the timers ending simultaneously.

There are several ways to address this.

1. Use setInterval

One way is to use setInterval(). When you call it once, it keeps running a function repeatedly every few milliseconds.

You'd need to keep track of how many times it runs, and then call clearInterval() when it's over to stop the looping.

let counter = 0;
const intervalId = setInterval(function () {
  console.log("Hello");
  counter++;

  if (counter === 10) {
    clearInterval(intervalId); // Stop the interval after 10 iterations
  }
}, 5000);

This way, the callback inside setInterval will be executed every 5 seconds, and you can control the number of iterations by incrementing the counter variable and clearing the interval when the desired count is reached.

2. Use the Loop Variable to Manage Delays

Utilize the loop variable to determine the delay for each function call. Adjust the delay by multiplying the loop variable with the desired time interval. In this case, the code could look like this:

function printHello() {
  console.log("Hello");
}

for (let i = 1; i <= 10; i++) {
  setTimeout(function () {
    printHello();
  }, i * 5000);
}

3. Controlled Delays Using async and await

Transform your functions into async functions and leverage the await keyword along with the provided sleep function for controlled delays:

let sleep = (dur) => new Promise((res) => setTimeout(res, dur));

async function runLoop() {
  for (let i = 0; i < 10; i++) {
    console.log("Hello");
    await sleep(500); // Wait for 500 milliseconds
  }
}

// Call the async function to start the loop
runLoop();

Get my free, weekly JavaScript tutorials

Want to improve your JavaScript fluency?

Every week, I send a new full-length JavaScript article to thousands of developers. Learn about asynchronous programming, closures, and best practices — as well as general tips for software engineers.

Join today, and level up your JavaScript every Sunday!

Thank you, Taha, for your amazing newsletter. I’m really benefiting from the valuable insights and tips you share.

- Remi Egwuda