Skip to main content

Command Palette

Search for a command to run...

The Event Loop

Updated
5 min read

well well well, I love this stuff, my eyes lit up when I understood the concept. I hope I can do justice to this amazing engineering.

before getting started, two important questions to answer.

1. Is nodejs single-threaded ?
YES

2. Node.js is also non-blocking.
YES

What the Event Loop really is

Think of the Event Loop as:

A disciplined scheduler that decides what code runs next and when

It repeatedly does this:

  1. Take one task

  2. Run it to completion

  3. Pick the next task based on strict rules

  4. Repeat forever

The event loop is like a conductor in an orchestra it doesn't play instruments itself, but it coordinates when each section (or phase) gets to perform. Every time the event loop completes one full cycle through all phases, we call that a tick.

The Six Phases

Phase 1: Timers Phase

What happens here - This phase executes callbacks scheduled by setTimeout() and setInterval() once their timer expires.

Key details:

  • If you set setTimeout(callback, 0), it doesn't mean "run immediately." It means "run in the next tick's timers phase."

  • The event loop checks if the timer's time has elapsed, and if so, executes those callbacks.

  • If one or more timers are ready, the event loop will wrap back to this phase to execute those timers' callbacks.

Phase 2: Pending Callbacks Phase

What happens here: Executes I/O callbacks that were deferred to the next loop iteration. Think of this as your "pending mail" folder. Some I/O operations (like TCP errors, certain file system operations) couldn't be handled immediately in the poll phase, so they're queued here for the next cycle.

Key details:

  • This phase handles callbacks that couldn't be processed in the previous poll phase.

  • Examples include certain types of TCP errors or operations that the system couldn't complete right away.

  • It's essentially a "catch-up" phase for I/O that needs attention but wasn't ready during the last poll.

Phase 3: Idle, Prepare Phase

What happens here: This phase is used internally by Node.js and libuv. This is like the orchestra's tuning phase musicians adjust their instruments before the main performance. You don't see it, but it's essential for everything to work smoothly.

Key details:

  • Idle phase: Used internally by libuv for housekeeping tasks.

  • Prepare phase: Also internal, used to prepare for the upcoming poll phase.

  • As a developer, you typically don't interact with this phase directly it's all behind the scenes magic.

Phase 4: Poll Phase Most important one

What happens here: This is the core phase where Node.js retrieves new I/O events and executes their callbacks.

Key details:

It has two main responsibilities:

  1. Calculate how long it should block and poll for I/O: The event loop figures out how long to wait for new events.

  2. Process I/O events in the poll queue: When I/O events are ready, their callbacks get executed here.

Also, it has a special behviour as well. f there are setImmediate() callbacks waiting AND the poll queue is empty, Node.js will immediately exit the poll phase and move to the check phase.
If timers are ready to fire, Node.js will wrap back to the timers phase after the poll phase completes. It can be made predictible. We’ll see that through code snippets.

Phase 5: Check Phase

What happens here: Executes callbacks scheduled by setImmediate(). Key details:

  • setImmediate() callbacks are designed to execute after I/O callbacks but before the next tick's timers.

  • This phase exists specifically to allow developers to schedule callbacks that run right after the poll phase.

  • If you have both setTimeout(callback, 0) and setImmediate(callback), the order depends on context—usually setTimeout fires first when called from the main module, but setImmediate can win inside I/O callbacks.

Phase 6: Close Callbacks Phase

What happens here: Handles "close" events and cleanup operations. For instance, a connection being terminated or a socket.close event.

Lets look at the complete cycle in action -

And the best way to understand this is to play classic “guess the output“ game

console.log(' Start');

// Timers phase
setTimeout(() => console.log(' setTimeout (timers phase)'), 0);

// Check phase
setImmediate(() => console.log('setImmediate (check phase)'));

// Poll phase (I/O callback)
require('fs').readFile(__filename, () => {
    console.log('readFile callback (poll phase)');
    setImmediate(() => console.log(' Nested setImmediate'));
});

// Close callbacks phase
const net = require('net');
const server = net.createServer();
server.listen(0, () => {
    server.close(() => {
        console.log('🚪 Close callback (close callbacks phase)');
    });
});

console.log('End');

/// * Output 
 Start
 End
 readFile callback (poll phase)
 setImmediate (check phase)
 Close callback (close callbacks phase)
 setTimeout (timers phase)
 Nested setImmediate
 /// *

The Takeaway

it's a very well-organized system with six distinct phases:

  1. Timers - Scheduled timeouts and intervals

  2. Pending Callbacks -Deferred I/O callbacks

  3. Idle/Prepare - Internal housekeeping

  4. Poll - The heart of I/O processing (where Node spends most time!)

  5. Check - setImmediate() callbacks

  6. Close Callbacks - Cleanup and resource destruction.

he event loop is powered by libuv a C library that handles all the low-level I/O operations and threading. Node.js sits on top of libuv, providing the JavaScript interface we all love. The Node.js official documentation has amazing diagrams of the event loop. Check it out if you want to go even deeper, I took a lot of inspiration from there and some other great blogs out there.