JavaScript: the event loop as a cooperative scheduler
Predict the order in which synchronous code, nextTick, promises, and timers run in Node.js.
On this page
JavaScript callbacks and promises.
- Predict the order in which synchronous code, nextTick, promises, and timers run in Node.js.
- Predict the sample output and explain the state changes.
- Complete the practice task and check the boundary cases.
The concept
JavaScript runs your code on one thread. Instead of preempting a running task, the runtime uses cooperative scheduling: a task runs to completion, and only then does the event loop pick the next one. Long synchronous work therefore blocks everything else.
In Node.js, after the current script finishes, the process.nextTick queue runs first, then promise callbacks (the microtask queue), and only then timers and I/O callbacks, which are macrotasks. A timer of 0 ms still waits for the current script and all microtasks. Knowing this order explains many surprising outputs.
Code example
console.log("script start");
setTimeout(() => console.log("timeout (macrotask)"), 0);
Promise.resolve().then(() => console.log("promise (microtask)"));
process.nextTick(() => console.log("nextTick"));
console.log("script end");script start script end nextTick promise (microtask) timeout (macrotask)
Follow the code
Open the walkthrough to inspect the example one step at a time.
Show guided walkthrough
console.log("script start"); setTimeout(() => console.log("timeout (macrotask)"), 0);Promise.resolve().then(() => console.log("promise (microtask)"));process.nextTick(() => console.log("nextTick")); console.log("script end");Follow the example
Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.
View the complete dry-run table
| Step / line | What happens | State after step | Output so far |
|---|---|---|---|
| Synchronous code runs first, in order. | — | script start | |
| The callback is queued for a later turn of the event loop. | — | script start | |
| The callback goes on the microtask queue. | — | script start | |
| The script finishes before any queued callback runs. | — | script start script end | |
| After the script ends, nextTick runs first, then the microtask, then the timer. | — | script start script end nextTick promise (microtask) timeout (macrotask) |
Behind the scenes
How JavaScript processes this code
A JavaScript engine parses and runs the source. A function body runs when the function is called. console.log writes to the console.
Cost and common mistakes
Each callback runs in O(1) scheduling time plus its own work.
- A long synchronous loop, or a CPU-heavy function, blocks timers, I/O, and every other callback.
- Endlessly scheduling nextTick or promise callbacks can starve timers and I/O because the microtask queues are drained completely first.
- The exact order of setTimeout(0) and setImmediate differs depending on context. Do not rely on it.
Try it yourself
Where would a setImmediate callback run in this order?
Reveal answer and reasoning
After the microtasks, in the check phase of the event loop. Relative to a 0 ms timer the order can vary when called from the main module.