Event Loop Deep Dive

Loading concept...

The Event Loop: Node.js’s Secret Superpower 🎒

Imagine a very smart waiter in a busy restaurant. This waiter never sleeps. He takes orders, delivers food, and handles everythingβ€”all by himself! But here’s the magic: he NEVER waits. If the kitchen is cooking your food, he goes to help other customers instead of standing around.

That waiter is the Event Loop.

Node.js has only ONE main waiter (one thread), but he’s so clever that he can serve thousands of customers at once!


What is the Event Loop? πŸ”„

Think of the Event Loop as a merry-go-round that never stops spinning.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚    Your Code Runs       β”‚
β”‚  "Hey, read this file!" β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Event Loop Catches    β”‚
β”‚   "I'll handle that!"   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Work Gets Done        β”‚
β”‚   (File is being read)  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Callback Runs         β”‚
β”‚   "Here's your file!"   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Simple Example:

console.log('Order placed!');

setTimeout(() => {
  console.log('Food is ready!');
}, 1000);

console.log('Waiting...');

Output:

Order placed!
Waiting...
Food is ready!  ← (after 1 second)

The Event Loop didn’t wait for the food. It kept going!


The Six Phases of the Event Loop 🎑

Our merry-go-round has six stations. Each time it spins, it visits every station in order.

graph TD A[⏰ Timers] --> B[πŸ“‹ Pending Callbacks] B --> C[πŸ”§ Idle/Prepare] C --> D[πŸ“‘ Poll] D --> E[βœ… Check] E --> F[πŸšͺ Close Callbacks] F --> A

Phase 1: Timers ⏰

What happens here? Callbacks from setTimeout() and setInterval() run here.

setTimeout(() => {
  console.log('Timer done!');
}, 100);

It’s like an alarm clock. When the time is up, the callback gets to run!


Phase 2: Pending Callbacks πŸ“‹

What happens here? Some system operations (like network errors) save their callbacks for later. They run here.

Think of it as a β€œsorry, couldn’t do it earlier” pile.


Phase 3: Idle/Prepare πŸ”§

What happens here? Node.js does internal housekeeping. You don’t write code for this phase.

It’s the waiter straightening his bowtie between orders!


Phase 4: Poll πŸ“‘

What happens here? This is the busiest station! Here, Node.js:

  • Fetches new I/O events (files, network, etc.)
  • Runs their callbacks
const fs = require('fs');

fs.readFile('menu.txt', (err, data) => {
  console.log('Menu loaded!');
});

When the file is ready, this callback runs in the Poll phase.


Phase 5: Check βœ…

What happens here? Callbacks from setImmediate() run here.

setImmediate(() => {
  console.log('Immediate!');
});

Phase 6: Close Callbacks πŸšͺ

What happens here? When things close (like a socket disconnecting), their cleanup callbacks run here.

socket.on('close', () => {
  console.log('Goodbye!');
});

process.nextTick(): The VIP Pass 🎟️

Remember our waiter? Well, process.nextTick() is like a VIP customer. They get served BEFORE the next phaseβ€”no matter what!

setTimeout(() => {
  console.log('Timer');
}, 0);

process.nextTick(() => {
  console.log('nextTick');
});

console.log('Main');

Output:

Main
nextTick    ← VIP goes first!
Timer

Why does this happen? process.nextTick() has its own special queue. After the current code finishes, Node.js checks this queue BEFORE moving to the next Event Loop phase.

graph TD A[Current Code Finishes] --> B{nextTick Queue?} B -->|Yes| C[Run All nextTick Callbacks] C --> B B -->|No| D[Move to Next Phase]

When to use it?

  • When you need something to run RIGHT after the current code
  • When you want to give users a chance to set up event handlers
function MyThing() {
  process.nextTick(() => {
    this.emit('ready');
  });
}

setImmediate(): The β€œAfter This Phase” Pass 🎫

setImmediate() is different. It says: β€œRun me in the CHECK phase of the CURRENT loop cycle.”

setImmediate(() => {
  console.log('Immediate');
});

setTimeout(() => {
  console.log('Timeout');
}, 0);

console.log('Main');

Output (usually):

Main
Timeout   ← (might vary)
Immediate ← (might vary)

Wait, why β€œmight vary”?

Inside the main module, the order between setTimeout(0) and setImmediate can vary. But inside an I/O callback, setImmediate ALWAYS runs first:

const fs = require('fs');

fs.readFile('file.txt', () => {
  setTimeout(() => {
    console.log('Timeout');
  }, 0);

  setImmediate(() => {
    console.log('Immediate');
  });
});

Output (always):

Immediate  ← Always first in I/O!
Timeout

Thread Pool & libuv: The Kitchen Staff πŸ‘¨β€πŸ³

Remember our waiter analogy? The waiter is fast, but he can’t cook! For heavy work, he needs the kitchen staffβ€”the Thread Pool.

What is libuv?

libuv is the engine room of Node.js. It provides:

  • The Event Loop
  • The Thread Pool
  • Async I/O operations
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚        Node.js             β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚     Your Code        β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚             ↓              β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚      Event Loop      β”‚  β”‚
β”‚  β”‚      (libuv)         β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚             ↓              β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚    Thread Pool       β”‚  β”‚
β”‚  β”‚    (4 workers)       β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

What uses the Thread Pool?

Uses Thread Pool πŸ‘¨β€πŸ³ Uses OS directly πŸ–₯️
File system (fs) Network I/O
DNS lookups TCP/UDP sockets
Crypto operations Pipes
Compression (zlib) TTY

Thread Pool Size

By default, you get 4 threads. You can change this:

// Must set before requiring anything!
process.env.UV_THREADPOOL_SIZE = 8;

Max: 1024 threads (but be careful!)

Example: Thread Pool in Action

const crypto = require('crypto');

const start = Date.now();

// 4 hash operations - uses thread pool
for (let i = 0; i < 4; i++) {
  crypto.pbkdf2('password', 'salt', 100000,
    64, 'sha512', () => {
    console.log(`Hash ${i+1}: ${Date.now()-start}ms`);
  });
}

Output (on 4-core machine):

Hash 1: 52ms
Hash 2: 52ms
Hash 3: 52ms
Hash 4: 53ms  ← All roughly same time!

They all finish together because 4 threads work in parallel!


Putting It All Together 🧩

Let’s trace through a complete example:

console.log('1: Start');

setTimeout(() => console.log('2: Timeout'), 0);

setImmediate(() => console.log('3: Immediate'));

process.nextTick(() => console.log('4: nextTick'));

Promise.resolve().then(() => console.log('5: Promise'));

console.log('6: End');

Output:

1: Start
6: End
4: nextTick     ← nextTick queue (VIP)
5: Promise      ← Microtask queue
2: Timeout      ← Timers phase
3: Immediate    ← Check phase

Why this order?

  1. Synchronous code runs first (1, 6)
  2. nextTick queue empties (4)
  3. Microtask queue empties (5 - Promises)
  4. Event Loop phases begin (2, 3)

Quick Reference Chart πŸ“Š

Feature When it runs Use case
process.nextTick() Before next phase Immediate priority
Promise.then() After nextTick Async operations
setTimeout(fn, 0) Timers phase Delayed execution
setImmediate() Check phase After I/O

The Big Picture πŸ–ΌοΈ

graph TD A[Your Code] --> B[nextTick Queue] B --> C[Microtask Queue] C --> D[Event Loop] D --> E[Timers] E --> F[Pending] F --> G[Idle] G --> H[Poll] H --> I[Check] I --> J[Close] J --> E H --> K[Thread Pool] K --> H

Remember This! 🧠

  1. Event Loop = The smart waiter who never waits
  2. 6 Phases = Six stations on a merry-go-round
  3. nextTick = VIP pass (runs before next phase)
  4. setImmediate = After I/O, guaranteed in Check phase
  5. Thread Pool = Kitchen staff for heavy work (4 workers by default)
  6. libuv = The engine that powers everything

You now understand how Node.js handles thousands of requests with just one thread. That’s the magic of the Event Loop! πŸŽ‰

Loading story...

No Story Available

This concept doesn't have a story yet.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.

Interactive Preview

Interactive - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.

No Interactive Content

This concept doesn't have interactive content yet.

Cheatsheet Preview

Cheatsheet - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.

No Cheatsheet Available

This concept doesn't have a cheatsheet yet.

Quiz Preview

Quiz - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.

No Quiz Available

This concept doesn't have a quiz yet.