One Thread, Many Pending Operations
The Event Loop
Node runs JavaScript on a single thread. The event loop coordinates pending async operations — IO, timers, promises, immediates.
What you'll learn
- Understand single-threaded vs concurrent
- Recognize event loop phases
- Know what blocks the loop
Node runs your JavaScript on one thread. The reason it handles thousands of concurrent connections without breaking a sweat is the event loop.
The Mental Model
Imagine a kitchen with one cook:
- You order a pizza (slow). The cook puts it in the oven and moves on.
- You order a salad (fast). The cook makes it.
- The pizza pings — cook grabs it from the oven.
- You order more salads. Each goes out fast.
The cook never waits on the oven — they keep handling fast orders while slow ones cook in the background.
Node is the cook. Your CPU-light JS code is the fast orders. IO (files, network, DB) is the pizza in the oven.
Phases
Each loop iteration (“tick”) visits these phases in order:
| Phase | What runs |
|---|---|
| Timers | setTimeout, setInterval callbacks whose time is up |
| Pending callbacks | A few I/O callbacks from previous loop |
| Idle, prepare | Internal |
| Poll | Most I/O callbacks (file reads, sockets) |
| Check | setImmediate callbacks |
| Close | close event callbacks |
Between every phase: queued microtasks (promise .then,
queueMicrotask) run.
Microtasks vs Macrotasks
console.log("1");
setTimeout(() => console.log("2 — timeout"), 0);
Promise.resolve().then(() => console.log("3 — promise"));
console.log("4");
// Output:
// 1
// 4
// 3 — promise
// 2 — timeout Promises (microtasks) flush before the next macrotask phase, so
3 prints before 2.
What Blocks the Loop
Synchronous CPU-heavy work blocks every other request:
// imagine 10 concurrent requests hit this handler
function handler(req, res) {
// pure CPU work — blocks for ~500ms
const result = expensiveSorting();
res.end(result);
} The 11th request waits ~5 seconds. The fix: move CPU work to a worker thread (covered later this chapter).
Don’t Block
The two ways to block:
- Long synchronous code — heavy loops, sync crypto, sync fs
- Sync IO inside async paths —
readFileSyncin a handler
Async by default. CPU work → workers. IO → promises.
Callbacks →