Nested timers
In short: many "nested" setTimeout calls (where each callback schedules the next timer) do not overflow the stack, but they can clog the task queue, cause time drift, throttling, and interface lag.
What exactly happens
- No stack overflow.
Each
setTimeoutschedules the next task in the macrotask queue and finishes the current one - the stack is cleared between calls. - The macrotask queue grows. If each callback schedules several new timers or does heavy work, tasks start to pile up and the UI starts to lag.
- Drift (accumulating delay). The "tick" time shifts: the callback runs no earlier than the specified delay, and even later if the stack was busy. Over long chains, the shift accumulates.
- Minimum delay and throttling.
Even at
0ms, browsers enforce a minimum interval (usually about 4 ms; in an inactive tab, up to ~1000 ms or more). Nested timers with very small delays will still be "clamped". - Microtasks between timers.
After each callback, all microtasks run (
Promise.then,queueMicrotask). If you create many microtasks inside a timer, they further lengthen each step of the chain. - Execution order. Timers are pulled from the queue by readiness time; when several become ready "at the same time," the order may differ between engines, so it's best not to rely on a specific order.
- Memory. If callbacks close over large objects and the chain is long or infinite, memory usage can grow (objects stay reachable for longer).
Example of a "nested" chain
javascript
function tick() {
// work...
setTimeout(tick, 0); // the next tick - on the next turn of the Event Loop
}
setTimeout(tick, 0);- The stack does not grow, but the "ticks" will run with a minimum-delay limit and with drift.
How to do it better
-
Compensate for drift in periodic tasks:
javascriptconst start = performance.now(); const step = 1000; // 1 sec let n = 0; function loop() { n++; // ...useful work... const nextAt = start + n * step; setTimeout(loop, Math.max(0, nextAt - performance.now())); } setTimeout(loop, step); -
For animations use
requestAnimationFrame- it is synchronized with repainting and drifts less. -
For "every N ms" without overlaps, prefer a recursive
setTimeout(as above) rather thansetInterval- this way you avoid accumulation if the callback sometimes takes longer than the interval. -
Move heavy work to a Web Worker so it does not block the UI.
Conclusion
Nested setTimeout calls are a safe way to "break up" synchronous recursion, but:
- you will not get exact timing,
- with a large number of tasks you will get lag and drift,
- the browser will introduce minimum delays and throttling.
Short Answer
Interview readyPremium
A concise answer to help you respond confidently on this topic during an interview.