Suggest an editImprove this articleRefine the answer for “Nested timers”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)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. **Key point:** the stack does not grow, because each `setTimeout` schedules the next task in the macrotask queue and finishes the current one.Shown above the full answer for quick recall.Answer (EN)ImageIn 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 1. **No stack overflow.** Each `setTimeout` schedules the *next* task in the macrotask queue and finishes the current one - the stack is cleared between calls. 2. **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. 3. **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. 4. **Minimum delay and throttling.** Even at `0` ms, 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". 5. **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. 6. **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. 7. **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: ```javascript const 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 than `setInterval` - 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.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.