NODE.JS GUIDE
process.nextTick() vs Promise Microtasks vs setImmediate()
By the HireReadyAI team · Last updated 6 October 2026 · 8 min read
The order that interviewers expect, and how overusing nextTick can starve I/O.
The order interviewers expect
After a callback finishes, Node drains the process.nextTick queue completely, then drains the promise microtask queue. Only after those queues are empty does the event loop continue to the next phase (timers, poll, check, and so on).
setImmediate callbacks run in the check phase. setTimeout(fn, 0) is scheduled for the timers phase. From the main module their relative order can vary; from inside an I/O callback, setImmediate typically runs before a zero-delay timer because check follows poll.
process.nextTick
nextTick schedules work before the event loop continues. Libraries use it to ensure a callback runs after the current operation completes but before I/O. It is more urgent than promises. That urgency is also the danger: a recursive nextTick loop never yields to timers or I/O.
Promise microtasks
Promise.then, queueMicrotask, and the continuation after await all enqueue microtasks. They run after nextTick callbacks for the current turn. async/await does not block the thread; it just splits your function across microtasks and later macrotasks when you await real I/O.
setImmediate and timers
setImmediate is a macrotask for the check phase. Prefer it when you want to run after I/O callbacks in the current loop iteration. Prefer setTimeout when you need a delay. Prefer promises/async for application flow. Prefer nextTick only when you must run before other microtasks and you understand the starvation risk.
How nextTick can starve I/O
Because nextTick is drained before the loop advances, scheduling nextTick from within a nextTick callback can prevent poll from ever running. Incoming sockets and timers sit waiting. Interviewers love asking why recursive setImmediate is safer: each setImmediate yields to the next loop iteration.
Worked example
process.nextTick(() => console.log('nextTick'));
Promise.resolve().then(() => console.log('promise'));
setImmediate(() => console.log('immediate'));
setTimeout(() => console.log('timeout'), 0);
console.log('sync');
Typical output starts with sync, then nextTick, then promise, then timeout/immediate depending on context. Explain the why, not only the letters on the screen.
Follow-up questions
- Where would you use nextTick in a library API design?
- How does await interact with the microtask queue?
- How would you measure event-loop delay in production?