A lot of beginners think:
setTimeout(callback, 1000);means "This will run exactly after 1 second."
But in reality, it means:
"Run the callback after at least 1 second, but possibly later, depending on the event loop and other tasks."
setTimeout isn't a stopwatch — it's just a scheduling request to the JavaScript runtime.
- You call
setTimeout(fn, delay). - The timer counts down in Web APIs (browser) or libuv (Node.js).
- When the timer finishes, the callback is placed in the macrotask queue.
- The event loop checks:
- Is the call stack empty?
- Have all microtasks finished?
- If yes, then run the callback.
If the call stack is busy (e.g., a long-running loop or many microtasks), the callback has to wait — even if the delay already expired.
console.log('Start');
setTimeout(() => console.log('Timeout'), 1000);
const start = Date.now();
while (Date.now() - start < 3000) {
// Blocking for 3 seconds
}
console.log('End');Expected by beginners:
Start
Timeout // after 1 second
End
Actual:
Start
End
Timeout // after ~3 seconds
- The call stack was blocked for 3 seconds.
- The timeout was ready after 1 second but couldn't run until the stack was free.
Browsers often clamp minimum delays:
- Nested timers may be throttled to a minimum of 4ms.
- In inactive tabs, timers may be clamped to 1000ms+ to save resources.
setTimeout(() => console.log('X'), 0);
console.log('Y');Output:
Y
X
Because 0 ms is not immediate — it still waits for the current task + microtasks to finish.
setTimeout has a "trust issue" because the specified delay is the minimum wait time, not an exact schedule. The actual execution time depends on the event loop — if the call stack or microtask queue is busy, the callback runs later. Also, browsers clamp very short delays and throttle timers in inactive tabs, so timing isn't precise. This is why setTimeout should be used for scheduling, not for accurate timing.