← All concepts
Numbers, Dates & Timers

Timers and Scheduling

setTimeout and setInterval schedule work for later, but the delay you give them is a minimum, not a promise.

Definition

setTimeout(fn, delay) schedules fn to run once, after at least delay milliseconds. setInterval(fn, delay) does the same thing repeatedly, firing fn again and again every delay milliseconds, until something stops it. Both hand their callback to the browser or runtime, which waits out the delay and then places the callback in the macrotask queue. It only actually runs once the call stack is completely empty and the event loop reaches it, so a delay of 0 does not mean immediately; it means as soon as everything already queued to run has finished. This is why the delay is described as a minimum. If the main thread is busy running other synchronous code when a timer's moment arrives, the callback waits in the queue until the stack clears, so the real-world gap between ticks can stretch well past the requested delay. clearInterval(id) (and clearTimeout(id) for a single timer) cancels a pending or repeating timer using the id that setInterval or setTimeout returned; once cleared, the callback will not run again, even if it was already due.

Examples

console.log("a");
setTimeout(() => console.log("b"), 0);
console.log("c");
// logs: a, c, b

Even with a 0ms delay, the timer callback is queued as a macrotask and only runs after the synchronous code (a, c) finishes and the stack is empty.

let count = 0;
const id = setInterval(() => {
  count++;
  console.log(count);
  if (count === 3) clearInterval(id);
}, 1000);

The callback fires roughly every 1000ms until it calls clearInterval, which cancels any future tick using the id setInterval returned.

Common mistakes

  • Assuming setTimeout(fn, 0) runs fn immediately; it still waits for the current synchronous code and the queue ahead of it.
  • Forgetting to store and later clear an interval id, leaving a setInterval running after the component or page no longer needs it.
  • Expecting setInterval's delay to be exact over a long run; a busy stack can delay individual ticks, and those delays are not made up later.

Key takeaways

  • setTimeout and setInterval schedule a callback for at least the given delay, never exactly the given delay.
  • A queued timer callback only runs once the call stack is empty and its turn comes up.
  • setInterval keeps firing on a cadence until clearInterval cancels it using the returned id.
  • A busy main thread can push a tick later than requested; the delay is a floor, not a guarantee.
Check your understanding

Why does setTimeout(() => console.log("done"), 0) not log "done" before code that runs right after it?

Where you see this

  • Polling a server for updates every few seconds with setInterval, and stopping the poll with clearInterval when the component unmounts.
  • Delaying a UI action, like showing a tooltip, with a short setTimeout so it does not flicker on quick mouse movements.
  • Building a countdown or a ticking clock display that updates once per second.
  • Debugging a UI update that arrives later than expected because a heavy synchronous task blocked the timer's turn on the stack.

Practice this

Further reading

Previous
Array Search Methods
Next
DOM Events and Delegation