When you first hear that Node.js is single-threaded, it might sound limiting. But if that’s the case, how does it handle thousands of concurrent requests efficiently?
The secret sauce behind Node.js’s high performance lies in its Event Loop — a core concept that enables asynchronous, non-blocking behaviour using a single thread. In this blog post, we’ll break down and understand how it works under the hood, and why it’s essential to writing performant Node.js applications.
What is the Event Loop?
By offloading actions (such as file reads, network calls, or timers) to the system and handling their results asynchronously once they are completed, this mechanism enables Node.js to carry out non-blocking I/O activities.
Node.js executes JavaScript code on a single thread, but libuv, a C++ library, does the heavy lifting (such as I/O) by using a thread pool and asynchronous OS APIs to carry out tasks in the background.
How It Works
Here’s a simplified overview of how things work:
- JavaScript code runs in a call stack.
- Asynchronous operations (like
setTimeoutorfs.readFile) are handed off to Node’s APIs. - Once completed, their callback functions are pushed to a callback queue.
- The event loop continuously checks whether the call stack is empty.
- If it is, it pushes a callback from the queue to the stack for execution.
Visual Breakdown
+-------------------------------+
| Call Stack |
+-------------------------------+
| Your JavaScript Code |
+-------------------------------+
| Event Loop |
+-------------------------------+
| Callback Queue |
| (timers, IO, setImmediate) |
+-------------------------------+
| System Kernel / libuv |
|(Async operations & threadpool)|
+-------------------------------+
Event Loop Phases (Simplified)
It runs in phases. Each phase processes specific kinds of callbacks:
- Timers Phase – Executes callbacks from
setTimeout()andsetInterval(). - Pending Callbacks Phase – Executes certain I/O callbacks deferred to the next loop.
- Poll Phase – Retrieves new I/O events and executes related callbacks.
- Check Phase – Executes callbacks from
setImmediate(). - Close Callbacks Phase – Handles callbacks like
socket.on('close').
Each phase is executed in a loop, continuously checking for queued tasks.
Microtasks vs Macrotasks
In addition to the phases, Node.js has two types of tasks:
Microtasks
Executed immediately after the current operation, and before the next phase begins.
process.nextTick()Promise.then(),Promise.catch()
Macrotasks
Scheduled for future phases.
setTimeout()setImmediate()- I/O callbacks
Example:
console.log('Start');
setTimeout(() => {
console.log('setTimeout');
}, 0);
setImmediate(() => {
console.log('setImmediate');
});
Promise.resolve().then(() => {
console.log('Promise');
});
process.nextTick(() => {
console.log('nextTick');
});
console.log('End');
Output:
Start
End
nextTick
Promise
setTimeout
setImmediate
Explanation:
nextTickandPromiseare microtasks, so they’re executed before moving to the next phase.setTimeoutandsetImmediateare macrotasks and run in their respective phases.
Common Mistakes with the Event Loop
- Blocking the event loop: Using synchronous code like
fs.readFileSync()in a high-load server can stall all requests. - Too many
process.nextTick()calls: Can starve the event loop and delay I/O tasks. - Forgetting to handle async errors: Not catching errors in promises or async/await can silently crash your app.
Best Practices
- Use async/await for readable asynchronous code.
- Avoid blocking the main thread with CPU-heavy operations.
- Use worker threads or child processes for heavy computation.
- Monitor event loop lag using tools like
clinic.jsorprocess.hrtime().
Conclusion
The core of Node.js is the Event Loop. Despite being single-threaded, it enables Node.js to manage I/O-intensive operations effectively. You can write code more quickly, neatly, and efficiently if you know how the event loop, microtasks, and macrotasks work together.
If you’re serious about mastering backend development with Node.js, mastering the event loop is non-negotiable. It is essential for creating scalable, high-performance applications.
Finally, for more such updates and to read more about such topics, please follow our LinkedIn page Frontend Competency.