Docs

12.4-Event-Loop

6.4 The Event Loop

Introduction

JavaScript is single-threaded, meaning it can only execute one piece of code at a time. Yet it handles asynchronous operations like timers, network requests, and user interactions seamlessly. The secret? The Event Loop.


The JavaScript Runtime Model

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                   JAVASCRIPT RUNTIME                          β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                               β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚  β”‚   CALL STACK    β”‚      β”‚         WEB APIs            β”‚    β”‚
β”‚  β”‚                 β”‚      β”‚  (Browser-provided)         β”‚    β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚      β”‚                             β”‚    β”‚
β”‚  β”‚  β”‚  current  β”‚  β”‚  ──► β”‚  β€’ setTimeout/setInterval   β”‚    β”‚
β”‚  β”‚  β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€  β”‚      β”‚  β€’ fetch / XMLHttpRequest   β”‚    β”‚
β”‚  β”‚  β”‚  waiting  β”‚  β”‚      β”‚  β€’ DOM events               β”‚    β”‚
β”‚  β”‚  β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€  β”‚      β”‚  β€’ geolocation              β”‚    β”‚
β”‚  β”‚  β”‚  global   β”‚  β”‚      β”‚                             β”‚    β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                    β”‚                     β”‚
β”‚           ↑                             β”‚                     β”‚
β”‚           β”‚                             ↓                     β”‚
β”‚           β”‚         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚           β”‚         β”‚          CALLBACK QUEUES            β”‚  β”‚
β”‚           β”‚         β”‚                                     β”‚  β”‚
β”‚           β”‚         β”‚  Macrotask Queue (Task Queue):      β”‚  β”‚
β”‚           β”‚         β”‚  [callback1] [callback2] [...]      β”‚  β”‚
β”‚           β”‚         β”‚                                     β”‚  β”‚
β”‚           β”‚         β”‚  Microtask Queue:                   β”‚  β”‚
β”‚           β”‚         β”‚  [promise1] [promise2] [...]        β”‚  β”‚
β”‚           β”‚         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚           β”‚                             β”‚                     β”‚
β”‚           β”‚                             β”‚                     β”‚
β”‚           └─────────── EVENT LOOP β—„β”€β”€β”€β”€β”€β”˜                     β”‚
β”‚                    (continuously checks)                      β”‚
β”‚                                                               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

How the Event Loop Works

The Basic Algorithm:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    EVENT LOOP ALGORITHM                      β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                              β”‚
β”‚  REPEAT FOREVER:                                             β”‚
β”‚                                                              β”‚
β”‚    1. Is the Call Stack empty?                               β”‚
β”‚       └── NO  β†’ Keep running current code                    β”‚
β”‚       └── YES β†’ Go to step 2                                 β”‚
β”‚                                                              β”‚
β”‚    2. Are there microtasks?                                  β”‚
β”‚       └── YES β†’ Run ALL microtasks                           β”‚
β”‚                 (promises, queueMicrotask)                   β”‚
β”‚       └── NO  β†’ Go to step 3                                 β”‚
β”‚                                                              β”‚
β”‚    3. Is it time to render? (browser only)                   β”‚
β”‚       └── YES β†’ Render (requestAnimationFrame, paint)        β”‚
β”‚       └── NO  β†’ Go to step 4                                 β”‚
β”‚                                                              β”‚
β”‚    4. Are there macrotasks?                                  β”‚
β”‚       └── YES β†’ Run ONE macrotask                            β”‚
β”‚                 (setTimeout, setInterval, I/O)               β”‚
β”‚       └── NO  β†’ Wait for new tasks                           β”‚
β”‚                                                              β”‚
β”‚    5. Go back to step 1                                      β”‚
β”‚                                                              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Synchronous vs Asynchronous

Synchronous (Blocking):

console.log('First');
console.log('Second');
console.log('Third');
// Output: First, Second, Third (in order)

Asynchronous (Non-Blocking):

console.log('First');

setTimeout(() => {
  console.log('Second'); // Async - goes to queue
}, 0);

console.log('Third');
// Output: First, Third, Second

setTimeout and the Event Loop

console.log('1. Start');

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

console.log('2. End');

Step-by-Step Execution:

Step 1: Execute "console.log('1. Start')"
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Call Stack   β”‚  β”‚  Task Queue   β”‚
β”‚               β”‚  β”‚               β”‚
β”‚ console.log() β”‚  β”‚    (empty)    β”‚
β”‚   global      β”‚  β”‚               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Output: "1. Start"

Step 2: Execute "setTimeout(...)"
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Call Stack   β”‚  β”‚  Web API      β”‚
β”‚               β”‚  β”‚               β”‚
β”‚ setTimeout()  β”‚  β”‚  Timer: 0ms   β”‚
β”‚   global      β”‚  β”‚  callback: () β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
(Timer starts in Web API)

Step 3: Execute "console.log('2. End')"
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Call Stack   β”‚  β”‚  Task Queue   β”‚
β”‚               β”‚  β”‚               β”‚
β”‚ console.log() β”‚  β”‚  [callback]   β”‚ ← Timer finished
β”‚   global      β”‚  β”‚               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Output: "2. End"

Step 4: Call stack empty, event loop moves callback
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Call Stack   β”‚  β”‚  Task Queue   β”‚
β”‚               β”‚  β”‚               β”‚
β”‚   callback    β”‚  β”‚    (empty)    β”‚
β”‚   global      β”‚  β”‚               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Output: "3. Timeout"

Macrotasks vs Microtasks

Macrotasks (Task Queue):

  • β€’setTimeout
  • β€’setInterval
  • β€’setImmediate (Node.js)
  • β€’I/O operations
  • β€’UI rendering events

Microtasks (Microtask Queue):

  • β€’Promise.then/catch/finally
  • β€’queueMicrotask()
  • β€’MutationObserver

Priority: Microtasks Run First!

console.log('1. Start');

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

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

console.log('2. End');

// Output: 1. Start, 2. End, 3. Promise, 4. Timeout

Why?

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    EXECUTION ORDER                           β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                              β”‚
β”‚  1. Sync code runs first (call stack)                        β”‚
β”‚     β†’ "1. Start"                                             β”‚
β”‚     β†’ setTimeout callback β†’ Task Queue                       β”‚
β”‚     β†’ Promise callback β†’ Microtask Queue                     β”‚
β”‚     β†’ "2. End"                                               β”‚
β”‚                                                              β”‚
β”‚  2. Call stack empty β†’ Check microtasks                      β”‚
β”‚     β†’ Run ALL microtasks                                     β”‚
β”‚     β†’ "3. Promise"                                           β”‚
β”‚                                                              β”‚
β”‚  3. Microtasks empty β†’ Run ONE macrotask                     β”‚
β”‚     β†’ "4. Timeout"                                           β”‚
β”‚                                                              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Nested Microtasks

Microtasks can add more microtasksβ€”all run before the next macrotask:

console.log('1. Start');

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

Promise.resolve()
  .then(() => {
    console.log('2. Promise 1');
    return Promise.resolve();
  })
  .then(() => {
    console.log('3. Promise 2');
  });

Promise.resolve().then(() => console.log('4. Promise 3'));

// Output: 1. Start, 2. Promise 1, 4. Promise 3, 3. Promise 2, 5. Timeout

Common Patterns

Deferring Execution:

// Run after current sync code
setTimeout(() => {
  console.log('Deferred');
}, 0);

// Higher priority deferring (microtask)
queueMicrotask(() => {
  console.log('Microtask deferred');
});

Breaking Up Long Tasks:

// BAD: Blocks the main thread
function processLargeArray(array) {
  for (let i = 0; i < array.length; i++) {
    heavyComputation(array[i]);
  }
}

// GOOD: Yields to event loop
function processLargeArrayAsync(array, index = 0) {
  if (index >= array.length) return;

  heavyComputation(array[index]);

  // Schedule next iteration
  setTimeout(() => {
    processLargeArrayAsync(array, index + 1);
  }, 0);
}

The Rendering Pipeline

In browsers, rendering happens between task queue processing:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    FRAME LIFECYCLE                           β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                              β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚  Task   β”‚ β†’ β”‚ Microtasks  β”‚ β†’ β”‚  Render  β”‚ β†’ β”‚  Task   β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚                                                              β”‚
β”‚  Task: setTimeout, events, etc.                              β”‚
β”‚  Microtasks: Promises, queueMicrotask                        β”‚
β”‚  Render: requestAnimationFrame, style, layout, paint         β”‚
β”‚                                                              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

requestAnimationFrame:

// Runs before next paint (smooth animations)
function animate() {
  element.style.left = parseInt(element.style.left) + 1 + 'px';
  requestAnimationFrame(animate);
}

requestAnimationFrame(animate);

Blocking the Event Loop

Long-running sync code blocks everything:

// DON'T DO THIS!
console.log('Start');

// Blocks for ~3 seconds
const end = Date.now() + 3000;
while (Date.now() < end) {
  // Busy loop - blocks event loop!
}

console.log('End');
// UI is frozen during the while loop

Signs of Blocking:

  • β€’Unresponsive UI
  • β€’Animations freeze
  • β€’Click events delayed
  • β€’"Page unresponsive" warnings

Node.js Event Loop Phases

Node.js has a more detailed event loop:

   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”Œβ”€β–Ίβ”‚           timers          β”‚ ← setTimeout, setInterval
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  β”‚     pending callbacks     β”‚ ← I/O callbacks
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  β”‚       idle, prepare       β”‚ ← internal only
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  β”‚           poll            β”‚ ← new I/O events
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  β”‚           check           β”‚ ← setImmediate
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
└───      close callbacks      β”‚ ← socket.on('close', ...)
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

process.nextTick vs setImmediate:

// process.nextTick - runs before the event loop continues
process.nextTick(() => console.log('nextTick'));

// setImmediate - runs in check phase (after I/O)
setImmediate(() => console.log('setImmediate'));

// Microtasks
Promise.resolve().then(() => console.log('promise'));

// Order: nextTick, promise, setImmediate

Summary

ConceptDescription
Event LoopMoves callbacks from queue to stack
Call StackWhere synchronous code executes
Task QueuesetTimeout, setInterval, I/O
Microtask QueuePromises, queueMicrotask
Web APIsBrowser-provided async operations
BlockingLong sync code freezes everything
Microtasks FirstAll microtasks run before next macrotask

Key Rules to Remember

  1. β€’Sync code runs first - always
  2. β€’Microtasks before macrotasks - promises before timeouts
  3. β€’ALL microtasks run before the next macrotask
  4. β€’Don't block - keep sync code short
  5. β€’setTimeout(fn, 0) doesn't mean "immediate" - it means "as soon as possible after current code"

Debugging Tips

  1. β€’Use console.log with numbered messages to trace order
  2. β€’Chrome DevTools Performance tab shows event loop blocking
  3. β€’Look for long-running sync operations
  4. β€’Use requestIdleCallback for non-urgent work

Next Steps

In the next section, we'll explore Memory Managementβ€”how JavaScript handles memory allocation and garbage collection.

.4 Event Loop - JavaScript Tutorial | DeepML