Suggest an editImprove this articleRefine the answer for “How do Timers and scheduling work in Node.js?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Node.js timers** - functions that schedule callbacks at specific event loop phases. ```js process.nextTick(() => console.log('1')); // microtask, first Promise.resolve().then(() => console.log('2')); // microtask, second setTimeout(() => console.log('3'), 0); // timers phase setImmediate(() => console.log('4')); // check phase ``` **Key:** microtasks (`nextTick`, Promises) always flush before any timer callbacks fire.Shown above the full answer for quick recall.Answer (EN)Image**Node.js timers** - functions that schedule code to run at specific phases of the event loop, not at a precise wall-clock time. ## Theory ### TL;DR - Four scheduling functions: `setTimeout`, `setInterval`, `setImmediate`, `process.nextTick` - `process.nextTick` and resolved Promises are microtasks - they always run before any I/O callbacks - `setImmediate` fires in the check phase, after I/O poll; `setTimeout(fn, 0)` fires in the timers phase - Timers guarantee a minimum delay, not an exact one - Outside an I/O callback, the order of `setTimeout(fn, 0)` vs `setImmediate` is non-deterministic ### Quick example ```js console.log('start'); process.nextTick(() => console.log('nextTick')); // microtask, first Promise.resolve().then(() => console.log('Promise')); // microtask, second setTimeout(() => console.log('setTimeout'), 0); // timers phase setImmediate(() => console.log('setImmediate')); // check phase console.log('end'); // Output: // start // end // nextTick // Promise // setTimeout (may swap with setImmediate outside I/O) // setImmediate ``` The microtask queue drains completely before the event loop moves to the next phase. That is the one rule that never changes. ### How the event loop schedules each timer Node.js runs its event loop in ordered phases. Each timer function hooks into a different one. `process.nextTick` is not part of the event loop at all. Node drains the nextTick queue after every single operation before handing control back to libuv. This means recursive nextTick calls will starve I/O entirely. `Promise.then` callbacks go into the microtask queue and run right after nextTick. From Node.js 11+, microtasks flush between each individual event loop callback, not just between phases. `setTimeout(fn, delay)` registers in the timers phase. With `delay = 0`, the actual minimum is roughly 1ms. Exact firing depends on how long the current phase takes and what the OS timer resolution is. `setImmediate` fires in the check phase, right after the poll phase completes. Inside an I/O callback, `setImmediate` always fires before `setTimeout(fn, 0)`. Outside one, do not assume any order. `setInterval` behaves like repeated `setTimeout` calls but does not account for callback execution time. Under load, intervals drift because the next timer is measured from when the previous callback was scheduled, not when it finished. ### Timer accuracy Timers mark a minimum threshold, not a clock tick: ```js const start = Date.now(); setTimeout(() => { console.log(`Delay: ${Date.now() - start}ms`); // Requested: 100ms. Actual: 101-115ms, more under load }, 100); ``` If the event loop is blocked by a synchronous operation, your timer fires late. There is no mechanism to interrupt a running synchronous block. Seen this surprise teams around billing jobs - they schedule a periodic sync with `setTimeout` and only discover the drift after their first traffic spike. ### Promisified timers (Node.js 15+) `timers/promises` gives you awaitable versions of all three: ```js const { setTimeout: sleep, setImmediate: immediate } = require('timers/promises'); async function example() { await sleep(1000); // waits 1 second await immediate(); // yields to the check phase console.log('done'); } ``` They also accept an `AbortSignal`, which makes cancellation straightforward: ```js const { setTimeout: sleep } = require('timers/promises'); const ac = new AbortController(); setTimeout(() => ac.abort(), 500); await sleep(2000, undefined, { signal: ac.signal }); // AbortError at 500ms ``` No timer IDs to track manually. No `clearTimeout` scattered across try/catch blocks. ### Common mistakes **1. Expecting exact timing for production jobs** ```js // Unreliable setTimeout(() => sendDailyReport(), 24 * 60 * 60 * 1000); ``` The process can restart, the event loop can be blocked, the system clock can drift. For anything important, use `node-cron` or a message queue. **2. Starving I/O with recursive nextTick** ```js function loop() { process.nextTick(loop); // I/O never gets through } loop(); ``` The nextTick queue drains fully before I/O. This blocks all network and file operations indefinitely. Use `setImmediate` when you just need to defer something. **3. setInterval drift under load** ```js // Callback takes 200ms, interval is 1000ms // Real gap: 800ms between end of callback and next fire setInterval(async () => { await processQueue(); // takes variable time }, 1000); ``` When execution time matters, use recursive `setTimeout` instead. Start the next timer after the current callback finishes. **4. Assuming setTimeout(0) always fires before setImmediate** ```js setTimeout(() => console.log('A'), 0); setImmediate(() => console.log('B')); // Output: A B or B A - depends on OS timer resolution at this exact moment ``` Inside an I/O callback, it is always B then A. Outside, the spec does not guarantee order. ### Real-world patterns - **Debounce:** `clearTimeout` + `setTimeout` on every invocation, for search inputs and resize handlers - **Exponential backoff:** recursive `setTimeout` with `Math.pow(2, attempt) * 1000` delay between API retries - **CPU work chunking:** `setImmediate` between iterations to yield to I/O without blocking the loop - **Request timeout:** `AbortController` + `setTimeout` + `clearTimeout` on response received - **Graceful shutdown:** `setTimeout(process.exit, 5000)` as a hard stop fallback after SIGTERM ### Follow-up questions **Q:** What is the difference between `process.nextTick` and `setImmediate`? **A:** `process.nextTick` runs before the event loop moves to any next phase, draining its entire queue first. `setImmediate` runs in the check phase, after I/O poll. The names are misleading: `setImmediate` is the one that actually defers to the next loop iteration. **Q:** Why is the order of `setTimeout(fn, 0)` vs `setImmediate` non-deterministic outside I/O? **A:** Because `setTimeout` with delay 0 sets a minimum of 1ms. When the script starts, if that 1ms has already passed by the time the timers phase checks, `setTimeout` fires first. If not, the loop moves to check phase and `setImmediate` fires first. OS timer resolution determines which case you hit. **Q:** Can `process.nextTick` crash or block the server? **A:** It will not overflow the call stack, but it will starve I/O. Queuing nextTick callbacks from inside nextTick callbacks means network events and file reads never execute. `setImmediate` is the safer choice when you just want to defer work. **Q:** How does Node sleep between timers when nothing else is happening? **A:** libuv calculates the nearest timer expiry and blocks the poll phase for exactly that duration at the OS level. So `setTimeout(fn, 1000)` with no other pending work does not spin the CPU. Node sleeps until the timer fires. **Q:** When should you prefer `timers/promises` over the callback API? **A:** Whenever you are inside an async function and need clean cancellation. The callback API has no built-in AbortSignal support and forces you to track timer IDs manually across try/catch blocks. ## Examples ### Execution order across all four scheduling functions ```js // Run this in Node.js to verify your mental model setImmediate(() => console.log('setImmediate')); setTimeout(() => console.log('setTimeout(0)'), 0); process.nextTick(() => console.log('nextTick')); Promise.resolve().then(() => console.log('Promise.then')); // Guaranteed: // 1. nextTick // 2. Promise.then // 3. setTimeout(0) or setImmediate (non-deterministic outside I/O) // 4. setImmediate or setTimeout(0) ``` nextTick and Promise.then always come first. Between setTimeout(0) and setImmediate, do not write code that depends on either order. ### Retry with exponential backoff ```js async function fetchWithRetry(url, maxAttempts = 4) { for (let attempt = 0; attempt < maxAttempts; attempt++) { try { const res = await fetch(url); if (!res.ok) throw new Error(`HTTP ${res.status}`); return await res.json(); } catch (err) { if (attempt === maxAttempts - 1) throw err; const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s await new Promise(resolve => setTimeout(resolve, delay)); } } } ``` Each failed attempt waits 1s, then 2s, then 4s. The final throw re-raises the original error to the caller without wrapping it. ### CPU-intensive work split with setImmediate ```js function processLargeArray(items, callback) { let index = 0; function processChunk() { const end = Math.min(index + 100, items.length); while (index < end) { callback(items[index++]); } if (index < items.length) { setImmediate(processChunk); // yield to I/O between chunks } } processChunk(); } ``` Without `setImmediate`, processing 100,000 items blocks the event loop for the entire duration. With it, incoming HTTP requests get handled between chunks. The tradeoff is that total processing time increases slightly.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.