Skip to main content

Command Palette

Search for a command to run...

Day 1: Deep Dive into the Event Loop in Node.js

Updated
3 min read

Have you ever wondered how JavaScript, despite being a single-threaded language, manages async tasks like setTimeout or fetch() without freezing? The secret lies in the Event Loop.

Today, I’m brushing up on one of the most fundamental Node.js internals that makes non-blocking I/O possible. 🚀

JavaScript is often called single-threaded. But then, how does it handle thousands of concurrent operations without freezing? The answer lies in the Event Loop. Let’s break it down step by step.


🔹 1. The Call Stack

At the core, JavaScript runs on a call stack.

  • Functions are pushed when called, popped when returned.

  • Errors show stack traces because they follow this order.

Example:

function multiply(a, b) { return a * b; }
function square(n) { return multiply(n, n); }
function printSquare(n) { console.log(square(n)); }

printSquare(4);

Execution order: printSquare → square → multiply → return → console.log.


🔹 2. The Blocking Problem

If a function takes too long (e.g., a while loop or synchronous request), it blocks the stack.

  • While blocked, nothing else runs.

  • Browser can’t repaint.

  • Node.js can’t handle new requests.

This is why most APIs in JS are asynchronous.


🔹 3. Async APIs Behind the Scenes

The JS engine itself doesn’t know about setTimeout, fetch, or file system calls.

  • In the browser, these are handled by Web APIs.

  • In Node.js, they’re handled by C++ APIs and libuv.

These APIs do the heavy lifting in parallel and return results later, not blocking the stack.


🔹 4. The Event Loop at Work

Here’s the process:

  1. You call an async function (setTimeout, fetch, I/O).

  2. That task is delegated to Web APIs (or Node’s APIs).

  3. Once finished, they push callbacks into a queue.

  4. The Event Loop constantly checks:

    • Is the stack empty?

    • If yes → move the first callback from the queue into the stack → execute.

Example:

console.log("Hi");
setTimeout(() => console.log("There"), 0);
console.log("Node.js");

Output:

Hi
Node.js
There

Even with setTimeout(0), the callback waits until the stack is clear.


🔹 5. Microtask Queue & Starvation

JavaScript has two queues:

  • Callback Queue: for timers, events, I/O.

  • Microtask Queue: for promises (.then, catch) and process.nextTick.

Rule: Microtasks always run before callbacks.

This can cause starvation:

function loop() {
  Promise.resolve().then(loop);
}
loop(); // callback queue never runs

Here, the callback queue (like setTimeout) is starved, because microtasks keep taking priority.


🔹 6. Rendering & Smoothness

The browser wants to repaint ~60 times per second (every 16.6ms).

  • If the stack is busy, it can’t render.

  • That’s why heavy loops or too many tasks → laggy UI.

  • In Node.js, the same blocking issue means incoming requests must wait.


🔹 7. Why Event Loop Matters for Engineers

  • Frontend: Don’t block the stack → smooth UIs.

  • Backend: Event loop + non-blocking I/O → handle thousands of connections.

  • System Design: Concepts like queues, tasks, and prioritization mirror real-world system design principles.

🔹Summary - Brushing Up Things

  • JS runs on a single call stack.

  • Async tasks rely on APIs outside the JS engine.

  • Event loop bridges the queue → stack.

  • Microtasks have higher priority than callbacks.

  • Don’t block the stack if you want scalable, responsive apps.


    That’s it for today’s deep dive into the Event Loop!!.
    Let’s meet tomorrow with another core Node.js concept!🚀

More from this blog

Node & Beyond

14 posts

Not just a blog. Definitely not clean code. Just me stumbling, learning, and sometimes celebrating small wins with Node.js and backend systems.