Node.js — Event Loop Order
"What does this print?" with process.nextTick, promises, setTimeout and setImmediate is
the most common Node interview question — and knowing the answer explains real bugs, like a
callback that runs "too early". You will both run code on Node's queues and predict the
order without running it.
A program is a list of steps { log, via, then? }. via says how the step is scheduled;
when it runs, it logs log, then schedules its then steps (if any) in order.
Part 1 — run(steps, log)
Schedule each step with the real API, so that it does log.push(step.log) thenrun(step.then ?? [], log):
via | API |
|---|---|
'sync' | run right away |
'nextTick' | process.nextTick(fn) |
'promise' | Promise.resolve().then(fn) |
'timeout' | setTimeout(fn, 0) |
'immediate' | setImmediate(fn) |
The driver calls run from inside an I/O callback (fs.readFile's), the way code inside
a request handler runs.
Part 2 — predict(steps)
Return the order of the logs without scheduling anything, by simulating four queues:
1. Executing a step logs it, then *enqueues* its children: a sync child executes immediately,
others go to the back of their queue.
2. After every callback (the initial I/O callback included), Node drains: every
nextTick (ticks added meanwhile included), then every promise callback (new ones
included); if that added ticks, start over.
3. Then the loop alternates phases, starting with the check phase because we come from an
I/O callback: run the immediate steps queued *before the phase started*, draining after
each one; then the timers phase does the same with timeout steps; then check, timers...
until both queues are empty.
Example
[{ log: 'timeout', via: 'timeout' }, { log: 'immediate', via: 'immediate' },
{ log: 'promise', via: 'promise' }, { log: 'tick', via: 'nextTick' }, { log: 'sync', via: 'sync' }]
// → ['sync', 'tick', 'promise', 'immediate', 'timeout']The driver returns { predicted, actual }: both must equal Node's real order.
Sample tests