NashTech Blog

Event Loop: Understanding the Asynchronous Programming

Table of Contents
node.js overview

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:

  1. JavaScript code runs in a call stack.
  2. Asynchronous operations (like setTimeout or fs.readFile) are handed off to Node’s APIs.
  3. Once completed, their callback functions are pushed to a callback queue.
  4. The event loop continuously checks whether the call stack is empty.
  5. 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:

  1. Timers Phase – Executes callbacks from setTimeout() and setInterval().
  2. Pending Callbacks Phase – Executes certain I/O callbacks deferred to the next loop.
  3. Poll Phase – Retrieves new I/O events and executes related callbacks.
  4. Check Phase – Executes callbacks from setImmediate().
  5. 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:

  • nextTick and Promise are microtasks, so they’re executed before moving to the next phase.
  • setTimeout and setImmediate are 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.js or process.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.

Picture of Devansh

Devansh

Leave a Comment

Your email address will not be published. Required fields are marked *

Suggested Article

Scroll to Top