Suggest an editImprove this articleRefine the answer for “What is the difference between process.nextTick() and setImmediate()?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**process.nextTick() vs setImmediate()**: `nextTick` drains a microtask queue before the event loop moves to any phase. `setImmediate` waits for the check phase, after I/O polling. Recursive `nextTick` starves I/O; `setImmediate` does not. ```js process.nextTick(() => console.log('nextTick')); // fires first setImmediate(() => console.log('setImmediate')); // fires in check phase // Output: // nextTick // setImmediate ``` **Key point:** use `setImmediate` by default; reach for `nextTick` only when you need to run before any I/O in the same turn.Shown above the full answer for quick recall.Answer (EN)Image**process.nextTick()** schedules a callback right after the current call stack empties, before the event loop advances to any phase. **setImmediate()** schedules a callback for the check phase, which runs after the poll (I/O) phase completes. ## Theory ### TL;DR - The event loop has phases: timers, poll (I/O), check, close. `nextTick` fires between any two phases; `setImmediate` only fires in the check phase. - `process.nextTick()` uses a microtask queue that V8 drains completely before `uv_run()` starts the next phase. `setImmediate()` pushes a `uv_check_t` handler into libuv. - Recursive `nextTick` starves I/O. Recursive `setImmediate` does not. - Decision rule: need to run before any I/O? Use `nextTick`. Want to yield to I/O and timers? Use `setImmediate`. - Both fire after synchronous code. Their relative order is deterministic only when called from inside an I/O callback. ### Quick example ```js setImmediate(() => console.log('I - setImmediate')); process.nextTick(() => console.log('N - nextTick')); Promise.resolve().then(() => console.log('P - Promise')); console.log('sync'); // Output (always): // sync // N - nextTick // P - Promise // I - setImmediate ``` `nextTick` fires first because V8 drains the microtask queue before the event loop advances. `Promise.then()` lives in that same queue but resolves after `nextTick` callbacks. `setImmediate` waits for the check phase. ### Key difference The gap between `nextTick` and `setImmediate` is not just timing, it is where in the engine the callback lives. `nextTick` callbacks sit in a dedicated queue that Node drains synchronously inside `InternalCallbackScope`, before `uv_run()` gets to do anything. `setImmediate` registers a `uv_check_t` handler in libuv, which only fires when the event loop reaches the check phase after polling for I/O. That is why `nextTick` can block I/O entirely if you recurse into it, and `setImmediate` cannot. ### When to use - Run before any I/O in the current cycle: `process.nextTick()` (e.g., emit an event after a constructor returns so listeners can attach first). - Yield to pending I/O or timers: `setImmediate()` (e.g., defer post-response cleanup in an Express route handler). - Recursive polling or retry loops: always `setImmediate`. `nextTick` recursion at depth >1k blocks timers by 100ms or more. - Consistent ordering with `Promise.then()`: `nextTick` fires before Promise callbacks in the same microtask drain cycle. ### Event loop priority | Priority | Mechanism | Queue / Phase | |----------|-----------|---------------| | 1 (highest) | `process.nextTick()` | Microtask (pre-loop) | | 2 | `Promise.then()` | Microtask (pre-loop) | | 3 | `setTimeout(fn, 0)` | Timers phase | | 4 | `setImmediate()` | Check phase | ### How Node handles them internally Node.js uses libuv for the event loop with phases in this order: timers, poll (I/O), check, close. `setImmediate()` registers a `uv_check_t` handler that libuv calls in the check phase via `uv__run_check()`, after `uv__io_poll()` finishes. `process.nextTick()` bypasses libuv entirely. V8 drains the nextTick queue inside `InternalCallbackScope` after any script or callback finishes, but before `uv_run()` continues to the next phase. That is why the Node.js docs describe `nextTick` as not technically part of the event loop. One thing I noticed in production: wrapping `process.exit()` in `nextTick` after an I/O callback feels harmless but can cut off other pending I/O operations. `setImmediate` in that pattern is safer. ### Common mistakes **Mistake 1: using nextTick for all async deferral** ```js fs.readFile('file.txt', (err, data) => { process.nextTick(() => process.exit(0)); // exits before other I/O completes }); ``` `nextTick` fires before the poll phase resumes, so other pending fs callbacks never run. Use `setImmediate` here. **Mistake 2: recursive nextTick in polling logic** ```js function poll() { process.nextTick(poll); // never yields to the event loop } poll(); ``` This starves all I/O and timers. At depth 1M+ the loop hangs indefinitely. Switch to `setImmediate(poll)` instead. **Mistake 3: expecting a nested setImmediate to fire before the outer one** ```js process.nextTick(() => { setImmediate(() => console.log('nested setImmediate')); }); setImmediate(() => console.log('outer setImmediate')); // Output: // outer setImmediate // nested setImmediate ``` The `setImmediate` queued inside a `nextTick` callback misses the current check phase and runs in the next cycle. This surprises people who expect the nested one to run first. **Mistake 4: starvation in recursive error handling** ```js setImmediate(() => console.log('I - setImmediate')); // queued let depth = 0; function recurse() { if (++depth > 5) return console.log('Done'); process.nextTick(recurse); } recurse(); // Output: // Done // I - setImmediate (delayed by 6 nextTick calls) ``` Six `nextTick` calls already delay `setImmediate` noticeably. At real scale (depth 1k+) you block timers by 100ms or more, which breaks timeout-sensitive code. ### Real-world usage - **Express route handlers**: `nextTick` to release a DB connection after `res.json()`, before the poll phase restarts. - **EventEmitter pattern**: defer `this.emit('ready')` in a constructor with `nextTick` so callers can attach listeners synchronously before the event fires. - **Hapi auth plugins**: `setImmediate` after request processing so pending network callbacks are not blocked. - **async_hooks**: `nextTick` runs inside the current async context without phase delay, useful for before/after instrumentation hooks. - **PM2 clustering**: `setImmediate` for inter-process message deferral so worker I/O is not starved. ### Follow-up questions **Q:** Can `setImmediate` ever fire before `process.nextTick()`? **A:** Not in Node.js >= 11 when both are called from top-level module code. In older versions or the REPL with an active timers phase, ordering was less predictable. In modern Node the microtask queue always drains before any phase. **Q:** How do `nextTick` and `Promise.then()` relate to each other? **A:** Both run in the microtask queue before the event loop advances. `nextTick` callbacks drain first, then Promise callbacks. So `process.nextTick(cb)` runs before `Promise.resolve().then(cb)` when both are queued in the same turn. **Q:** What happens when `nextTick` is called from inside a `setImmediate` callback? **A:** The `nextTick` callback runs immediately after the `setImmediate` callback returns, before the check phase moves to the next `setImmediate` in the queue. It does not wait for the next event loop iteration. **Q:** (Senior) In libuv source, what handles `setImmediate` vs `nextTick` dispatch? Walk through execution order. **A:** `setImmediate` registers a `uv_check_t` handle. libuv calls these in `uv__run_check()` inside `uv_run()`, after `uv__io_poll()`. `nextTick` never enters libuv: Node's `InternalCallbackScope` destructor flushes the nextTick queue via `MicrotaskQueue::PerformCheckpointInternal()` in V8 after each C++ callback boundary. So `nextTick` fires in the gap between any two libuv callbacks, not in a named phase. **Q:** How would you diagnose `nextTick` starvation in production? **A:** Use `clinic.js doctor` or run Node with `--trace-event-categories v8`. Clinic shows event loop lag over time. If loop delay spikes while CPU stays low, recursive `nextTick` is the usual suspect. Add a depth counter and switch to `setImmediate` after N iterations. ## Examples ### Basic: side-by-side ordering ```js console.log('1'); process.nextTick(() => console.log('2 - nextTick')); Promise.resolve().then(() => console.log('3 - Promise')); setImmediate(() => console.log('4 - setImmediate')); setTimeout(() => console.log('5 - setTimeout'), 0); console.log('6'); // Output (Node 18): // 1 // 6 // 2 - nextTick // 3 - Promise // 4 - setImmediate (or 5 then 4 - relative order of setImmediate and setTimeout is not guaranteed outside I/O) ``` `nextTick` and `Promise` drain the microtask queue synchronously before any loop phase. `setImmediate` and `setTimeout(fn, 0)` both run in loop phases, and their relative order outside an I/O callback is not guaranteed. ### Intermediate: EventEmitter constructor pattern ```js const EventEmitter = require('events'); class MyEmitter extends EventEmitter { constructor() { super(); // nextTick defers emit until after the constructor returns, // giving the caller time to call .on('ready', handler) first process.nextTick(() => { this.emit('ready'); }); } } const emitter = new MyEmitter(); emitter.on('ready', () => console.log('ready!')); // Output: ready! // Without nextTick, 'ready' fires during the constructor and no listener is attached yet ``` This is probably the most common legitimate use of `nextTick` in library code. `setImmediate` would also work here, but `nextTick` guarantees the emit happens before any I/O in the same turn. ### Senior: starvation in practice ```js setImmediate(() => console.log('setImmediate - should fire soon')); let depth = 0; function recurse() { if (++depth > 5) return console.log(`Done at depth ${depth}`); process.nextTick(recurse); } recurse(); // Output: // Done at depth 6 // setImmediate - should fire soon <-- delayed by all 6 nextTick calls // At depth 1_000_000, setImmediate and any timers // would be blocked for hundreds of milliseconds. // Fix: replace process.nextTick(recurse) with setImmediate(recurse) ``` The `setImmediate` callback queued before `recurse()` does not fire until the entire `nextTick` chain finishes. In a real scenario with a logger or health-check using recursive `nextTick`, this is how you quietly break timeout-sensitive operations.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.