ENGINEERING NOTES
Node.js Event Loop: Microtasks, Timers and I/O
Reason about callback scheduling, blocking work and why timer delays are thresholds rather than guarantees.
What problem does this solve?
An API can become slow while database response times remain stable. A synchronous parser, compression loop or large JSON operation can monopolise the JavaScript thread and delay every request sharing that event loop.
What is it?
The event loop schedules callbacks when asynchronous work is ready. It allows one JavaScript thread to coordinate many operations; it does not make a CPU-heavy JavaScript function execute in parallel.
Mental model
Picture a dispatcher who handles one callback at a time. Delegating I/O frees the dispatcher to serve another request. A callback that never returns prevents the dispatcher from checking completed work.
How it works internally
libuv coordinates event-loop phases and platform I/O. Some operations use its worker pool. Promise reactions and queueMicrotask callbacks run at microtask checkpoints, outside the phase diagram. process.nextTick has a separate queue; recursive scheduling can starve I/O. Top-level CommonJS and ES module execution can produce different nextTick/Promise ordering. Since Node 20/libuv 1.45, timers run after poll; do not base correctness on a race between setTimeout(0) and setImmediate.
Production example
For an upload endpoint, use streaming where possible, bound input size and move expensive CPU transformations to worker threads or another process. Making a CPU function async does not move its synchronous body off the event loop.
Node.js / TypeScript implementation
Run this small scheduling probe in Node. The synchronous messages precede the microtask, which precedes the timer in this example. The timer's delay is a minimum eligibility threshold, not a deadline.
console.log("start");
setTimeout(() => console.log("timer"), 0);
queueMicrotask(() => console.log("microtask"));
console.log("end");
// start, end, microtask, timerCommon mistakes
Awaiting an already resolved promise repeatedly is not a reliable way to yield to I/O. Neither promises nor process.nextTick are substitutes for worker threads when the work itself is expensive.
Performance / reliability considerations
Measure event-loop delay alongside request latency and CPU usage. Place limits on payloads and per-request work. If yielding a long loop with setImmediate, remember that other requests may observe intermediate state between chunks.
Interview questions
Why can one CPU loop delay unrelated requests? Which work uses a worker pool? Why is the order of a zero-delay timer and setImmediate context-dependent? Explain concurrency versus parallel execution.
Quick Revision
One callback runs at a time on a JavaScript thread. Nonblocking I/O enables concurrency. Microtasks run before returning to normal event-loop work and can starve it. Timer delays are thresholds. CPU work still blocks unless partitioned or moved to workers. Check the module context and Node version before predicting subtle scheduling order.