Harith Zahid

The Event Loop

Full Event Loop Diagram

   ┌───────────────────────────┐
┌─>│           timers          │  (setTimeout, setInterval)
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │     pending callbacks     │  (I/O callbacks from previous loop)
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │       idle, prepare       │  (internal Node.js use)
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐      ┌───────────────┐
│  │           poll            │<─────┤  incoming:    │
│  │  (retrieve new I/O events)│      │  connections, │
│  └─────────────┬─────────────┘      │  data, etc.   │
│  ┌─────────────┴─────────────┐      └───────────────┘
│  │           check           │  (setImmediate callbacks)
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
└──┤      close callbacks      │  (e.g., socket.on('close'))
   └───────────────────────────┘

Phase Descriptions

Phase What Happens
Timers Executes setTimeout() and setInterval() callbacks
Pending Callbacks Executes I/O callbacks deferred from previous loop
Idle, Prepare Internal Node.js operations
Poll Retrieves new I/O events, executes I/O callbacks (most time spent here)
Check Executes setImmediate() callbacks
Close Callbacks Executes close event callbacks (e.g., socket.on('close'))

Microtasks (Run Between Every Phase!)

Critical: Between EVERY phase, Node.js runs microtasks:

┌─ Microtasks (run between EVERY phase) ─┐
│  1. process.nextTick() queue            │ ← Highest priority
│  2. Promise callbacks (.then, .catch)   │ ← Second priority
└─────────────────────────────────────────┘

Complete Execution Order

Execution Stack (Synchronous code)
      ↓
process.nextTick queue  ← Runs first
      ↓
Promise microtask queue ← Runs second
      ↓
┌─────────────────────┐
│  Timers Phase       │
├─────────────────────┤
│  [microtasks run]   │ ← After each phase!
├─────────────────────┤
│  Pending Callbacks  │
├─────────────────────┤
│  [microtasks run]   │
├─────────────────────┤
│  Poll Phase         │
├─────────────────────┤
│  [microtasks run]   │
├─────────────────────┤
│  Check Phase        │  ← setImmediate
├─────────────────────┤
│  [microtasks run]   │
├─────────────────────┤
│  Close Callbacks    │
├─────────────────────┤
│  [microtasks run]   │
└─────────────────────┘
      ↓
   Repeat forever

Example: Complete Order

console.log('1: Sync');

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

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

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

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

console.log('6: Sync');

Output:

1: Sync
6: Sync
4: nextTick          ← Microtask (highest priority)
5: Promise           ← Microtask
2: setTimeout        ← Timer phase
3: setImmediate      ← Check phase

Explanation:

  1. All synchronous code runs first (1, 6)
  2. Microtasks run before entering event loop phases:
    • process.nextTick() first (4)
    • Promises second (5)
  3. Event loop phases begin:
    • Timers phase (2)
    • Poll phase (nothing here)
    • Check phase (3)