Node.js is designed around a single-threaded event loop. While this model is highly efficient for I/O-bound tasks, executing CPU-heavy operations on the main thread will block the event loop, causing all concurrent requests to hang.
This guide details the phases of the Event Loop and explains how to prevent blocking operations.
The Event Loop Phases
At a high level, the event loop processes tasks across distinct queues:
1. Poll: Executes I/O callbacks (database and network calls).
2. Check: Executes setImmediate() callbacks.
3. Timer: Executes setTimeout() and setInterval() callbacks.
4. Close: Executes closing callbacks (e.g. socket.on('close')).
Identifying Blocking Code
Any operation that takes longer than 10ms (such as JSON parsing of massive payloads, cryptographic hashing, or array sorting) blocks the event loop.
Using Worker Threads
For CPU-bound operations (such as processing images or parsing large CSVs), offload the task to a Worker Thread. This handles the computations in a separate operating system thread, leaving the main Event Loop free to serve incoming API traffic.
import { Worker } from 'worker_threads';
export function runCpuTask(data: any): Promise<any> {
return new Promise((resolve, reject) => {
const worker = new Worker('./worker-task.js', { workerData: data });
worker.on('message', resolve);
worker.on('error', reject);
});
}Summary
Never block the event loop. Split heavy tasks using setImmediate() or offload them to separate worker threads.