iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more
The Node.js event loop coordinates when JavaScript callbacks for asynchronous work get to run. It lets Node.js make progress while waiting for I/O, but it does not run JavaScript callbacks simultaneously: each callback runs to completion on the event-loop thread. That distinction explains why a timer can run late, why setImmediate() and setTimeout() do not always have a fixed order, and why CPU-heavy JavaScript can stall a server.
What the Node.js event loop does
Node.js runs the current JavaScript operation first. That code might register an event handler, schedule a timer, or begin an asynchronous operation such as file or network I/O. When the current operation finishes, the event loop processes callbacks that are ready to run.
While JavaScript is not running, the operating system can handle many I/O operations. Some tasks that do not have a suitable non-blocking operating-system facility, along with selected expensive tasks, may use libuv’s worker pool. When the work completes, its callback still runs as JavaScript on the event-loop thread.
Think of a checkout clerk handling one customer at a time while other systems fetch items or process requests in the background. Those external tasks can progress in parallel, but once the clerk begins one task, it must finish before the next task can be served. The analogy is imperfect: Node.js delegates work to both the operating system and, for some tasks, a worker pool, and its loop has distinct phases.
#1 Best Overall
How the event-loop phases fit together
Node.js describes the event loop as a series of phases, each with a queue of callbacks. The phase model is simplified, and platform details can differ. For a beginner, the important phases are:
- Timers: callbacks for
setTimeout()andsetInterval()become eligible after their time thresholds. - Pending callbacks: certain deferred system callbacks are handled here.
- Poll: Node.js retrieves and processes most I/O events. It may wait here when no work is ready.
- Check: callbacks scheduled with
setImmediate()run here, after poll callbacks. - Close callbacks: some close events, such as an abrupt socket closure, are delivered here.
Idle and prepare are internal steps that most application-level explanations can leave out. The key point is that a callback runs when its work is ready and the loop reaches the appropriate phase—not necessarily at the exact moment an operation finishes or a timer reaches its threshold.
Rank #2
Why a timeout does not run exactly on time
setTimeout(fn, delay) sets a threshold after which its callback may run; it is not an exact deadline. The Node.js event-loop guide puts it this way: “A timer specifies the threshold after which a provided callback may be executed rather than the exact time a person wants it to be executed.”
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →For example, if a 100 ms timeout becomes eligible while another JavaScript callback is still running, the timeout callback must wait for that callback to finish and for the loop to reach the timer phase. A callback that takes 10 ms after an I/O completion can therefore push a nominal 100 ms timer later than 100 ms after it was scheduled.
Rank #3
Similarly, setTimeout(fn, 0) does not mean “run immediately.” It schedules a timer with a zero-millisecond threshold, and the callback runs only when the loop can process it.
How setImmediate, setTimeout, nextTick, and queueMicrotask differ
These APIs schedule work at different points in relation to the current operation and the event loop. They are not interchangeable ways to request a precise delay.
Rank #4
| API | When the callback becomes eligible | Ordering and practical distinction |
|---|---|---|
setTimeout(fn, delay) |
After the timer threshold has elapsed. | It can run later than the threshold because other work and scheduling can delay it. With a zero delay, its ordering against setImmediate() depends on context. |
setImmediate(fn) |
In the check phase, after poll callbacks. | When scheduled from an I/O callback, Node.js’s documented example runs the immediate first. When both APIs are scheduled from the main module, either may run first. |
process.nextTick(fn) |
After the current JavaScript operation completes, before the event loop continues. | Recursive scheduling can prevent the loop from reaching poll and starve I/O. The current Node.js v26.10.0 process documentation marks it Legacy and recommends queueMicrotask() for most uses. |
queueMicrotask(fn) |
As a microtask after the current JavaScript operation. | Node.js recommends it over process.nextTick() for most uses. Do not treat either as a timer or as a promise of an exact elapsed time. |
The exact order depends on where scheduling happens. For instance, these calls from the main module do not establish a universal order:
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
By contrast, inside an I/O callback, Node.js’s documented example runs setImmediate() before a zero-delay timeout because the immediate is handled in the check phase after poll. Avoid relying on one ordering outside the context where it is documented.
What changed in Node.js 20
Starting with Node.js 20, which uses libuv 1.45.0, timers began running after the poll phase in each loop iteration. Earlier versions ran timers both before and after poll. The Node.js guide notes that this change can affect interactions between timers and immediates, so examples about their ordering should be understood in their specific scheduling context and runtime version.
Why CPU-heavy JavaScript blocks Node.js
Asynchronous I/O can let Node.js serve many clients with a small number of threads, but a long JavaScript callback still occupies the event-loop thread until it returns. During that time, the loop cannot service other ready callbacks. This can reduce throughput and make a service vulnerable if crafted input triggers expensive synchronous computation.
- Keep event handlers and other callbacks short.
- Avoid expensive synchronous work on the event-loop thread when other requests need timely service.
- Use an appropriate worker strategy for genuinely CPU-intensive work.
- Do not assume that marking work “asynchronous” makes CPU-heavy JavaScript non-blocking; asynchronous I/O and JavaScript execution are different concerns.
For a fuller treatment of the event-loop and worker-pool distinction, see the official Node.js guide, Don’t Block the Event Loop (or the Worker Pool).
Quick Recap
Further reading in the official Node.js documentation
- The Node.js Event Loop explains the phases, timers, immediates, and next-tick behavior.
- Timers | Node.js v26.10.0 Documentation documents current timer and immediate APIs.
- Process | Node.js v26.10.0 Documentation documents the current stability status of
process.nextTick().
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

