What Is the Event Loop and What Is It Used For?
The Event Loop is a core mechanism that allows Node.js to perform asynchronous operations. It is an infinite loop that processes tasks from queues and runs as long as there are scheduled tasks to handle.
Why it matters:
<inline-code>perf_hooks<inline-code>)Node.js executes user code on a single thread. Blocking operations such as filesystem access or cryptography are handled in auxiliary threads via a thread pool.
A callback always runs to completion. This eliminates many synchronization issues typical of multithreaded systems.
The event loop becomes overwhelmed by too many asynchronous operations and cannot keep up with processing.
Heavy computations running on the main thread block the entire event loop.
Chunking
Split long-running operations into smaller pieces and yield using <inline-code>setImmediate()<inline-code>
Worker Threads
Parallel JavaScript execution within a single process (stable since Node.js v12 LTS)
Each worker has its own event loop and V8 instance
Communication:
Child Processes / Cluster
Worker communication:
<inline-code>postMessage()<inline-code>Simpler, uses structured clone (data copying), suitable for small messages
<inline-code>SharedArrayBuffer<inline-code>Shared memory without copying, requires <inline-code>Atomics<inline-code> for synchronization, suitable for large datasets or frequent communication.
// postMessage - simpler, copies data
worker.postMessage({ type: 'process', data: myArray });
worker.on('message', (result) => console.log(result));
// SharedArrayBuffer - shared memory, no copying
const shared = new SharedArrayBuffer(1024);
const arr = new Int32Array(shared);
worker.postMessage({ buffer: shared });
// Worker can read/write directly to arr
// Atomics.add(arr, 0, 1) - thread-safe increment
The event loop is not part of the JavaScript engine (V8/JSC). It is implemented externally.
Macrotasks
<inline-code>setTimeout<inline-code><inline-code>setInterval<inline-code><inline-code>setImmediate<inline-code>Microtasks (Higher Priority)
<inline-code>queueMicrotask<inline-code>nextTick Queue (Highest Priority)
Between event loop phases, the nextTick queue is fully drained first, then the microtask queue, and only then does the loop proceed to the next phase.

This rule also applies between phases. Both queues always take precedence over macrotasks.
A Promise enters the event loop only when it is resolved or rejected, not while it is pending.

Exit phase notes:
nextTick and microtasks are processed:
Both queues are always fully drained before continuing.
Important details:
⚠️ Breaking change: This may affect application timing
What changed:
The phases remain the same (timers in the timers phase, <inline-code>setImmediate<inline-code> in the check phase). What changed is when timers are checked for expiration.
Before Node.js 20:

Since Node.js 20:

Impact: Under load (a saturated poll phase), timers may fire later than before.
Recommendation: Do not rely on <inline-code>setTimeout(fn, 0)<inline-code> for time-sensitive operations. Monitor event loop lag.
APM tools track event loop lag automatically:
Manual measurement: <inline-code>perf_hooks.monitorEventLoopDelay()<inline-code>
console.log('start');
const immediate = setImmediate(() => console.log('immediate'));
setTimeout(() => {
console.log('timeout');
clearImmediate(immediate);
}, 0);
console.log('end');Output:
<inline-code>start<inline-code>, <inline-code>end<inline-code>, then either <inline-code>timeout<inline-code> or <inline-code>immediate, timeout<inline-code> depending on whether the timeout is ready when entering the loop.
async function main() {
console.log('start');
let run = true;
setTimeout(() => {
console.log('timeout');
run = false;
}, 0);
while (run) {
await Promise.resolve();
}
}
main();Output:
If you’re running Node.js in production and want to avoid performance surprises, we’re happy to help. Feel free to reach out if you want a second opinion or a deeper discussion.