JavaScript
setTimeout
programming
web development
asynchronous

When does setTimeout start counting down?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

setTimeout is simple on the surface, but timing behavior depends on the event loop, call stack load, and browser throttling rules. The timer does not guarantee exact execution time, only a minimum delay before callback eligibility. Understanding when countdown starts and when callback runs prevents subtle asynchronous bugs.

Core Sections

When the Countdown Starts

The setTimeout countdown starts immediately when JavaScript executes the setTimeout call. If you pass 2000, the runtime schedules callback eligibility for at least two seconds later.

javascript
1console.log("A", Date.now());
2setTimeout(() => {
3  console.log("B", Date.now());
4}, 2000);
5console.log("C", Date.now());

Logs show A and C first because setTimeout only registers work. The callback cannot run until the current call stack is empty and the event loop reaches timer tasks.

Minimum Delay Versus Actual Execution

A timer delay is a lower bound, not a precise schedule. If the main thread is busy, callback execution can be delayed well beyond the requested value.

javascript
1setTimeout(() => console.log("timer fired"), 100);
2
3const start = Date.now();
4while (Date.now() - start < 800) {
5  // block main thread for 800 ms
6}
7
8console.log("loop finished");

Even with 100 ms delay, callback runs after the blocking loop completes. This is normal event-loop behavior.

Event Loop Queue Position

Timers become queued tasks after their delay expires. They still wait behind currently running JavaScript and other queued tasks.

javascript
setTimeout(() => console.log("timeout"), 0);
Promise.resolve().then(() => console.log("microtask"));
console.log("sync");

Output order is usually:

  1. sync
  2. microtask
  3. timeout

Microtasks run before timer tasks in the same loop turn.

Nested Timers and Clamping

Browsers apply clamping rules to repeated nested timers, often enforcing a minimum around a few milliseconds. In inactive tabs, throttling can be much stronger to save resources.

javascript
1let count = 0;
2function tick() {
3  count += 1;
4  console.log("tick", count, Date.now());
5  if (count < 5) setTimeout(tick, 1);
6}
7setTimeout(tick, 1);

You may not get exact one millisecond intervals, especially under background tab conditions.

Measuring Real Delay

If accurate timing matters, measure actual elapsed time rather than trusting delay values.

javascript
1const requested = 500;
2const start = performance.now();
3
4setTimeout(() => {
5  const actual = performance.now() - start;
6  console.log({ requested, actual });
7}, requested);

This makes latency visible and helps diagnose blocking work or throttling.

Better Patterns for Repeated Work

For periodic tasks, setInterval can drift if callbacks take longer than interval duration. Recursive setTimeout with drift correction can be more predictable.

javascript
1const interval = 1000;
2let next = performance.now() + interval;
3
4function run() {
5  const now = performance.now();
6  doWork();
7  next += interval;
8  setTimeout(run, Math.max(0, next - now));
9}
10
11setTimeout(run, interval);
12
13function doWork() {
14  console.log("run", new Date().toISOString());
15}

This keeps repeated execution closer to target cadence under light load.

Choosing Between setTimeout and requestAnimationFrame

For visual updates, requestAnimationFrame aligns callback execution with browser paint cycles and often feels smoother than timers.

javascript
1function animate() {
2  updateVisualState();
3  requestAnimationFrame(animate);
4}
5requestAnimationFrame(animate);

Use setTimeout for generic delayed tasks and requestAnimationFrame for animation loops. Picking the right primitive reduces jitter and timing surprises in UI code.

Common Pitfalls

  • Assuming timer callbacks run exactly at delay boundaries.
  • Blocking the main thread and then blaming timer accuracy.
  • Using setTimeout(fn, 0) as if it runs before microtasks.
  • Ignoring background-tab throttling in browser timing logic.
  • Using setInterval without handling callback duration and drift.

Summary

  • Countdown starts when setTimeout is executed.
  • Delay is minimum wait time, not guaranteed execution timestamp.
  • Callback runs only when event loop can process timer tasks.
  • Main-thread blocking and browser throttling affect observed timing.
  • Measure actual elapsed time for timing-sensitive features.

Course illustration
Course illustration

All Rights Reserved.