What will be the output? (Promises & Event Loop)

Medium

This problem demonstrates the JavaScript event loop execution order: synchronous code → microtasks → macrotasks.

Asked In

Event Loop Execution Order

JavaScript uses an event loop to handle asynchronous operations. The execution order follows this priority:

1. Synchronous code (executes immediately)

2. Microtasks (Promise.then, Promise.catch, queueMicrotask)

3. Macrotasks (setTimeout, setInterval, I/O operations)

Step-by-Step Execution

(async function () {
  console.log(1);                    // 1️⃣ Synchronous - Output: 1
  setTimeout(() => console.log(2), 0); // ⏰ Macrotask - Queued
  Promise.resolve().then(() => console.log(3)); // 🔵 Microtask - Queued
  setTimeout(() => console.log(4), 0); // ⏰ Macrotask - Queued
  console.log(5);                    // 2️⃣ Synchronous - Output: 5
  Promise.resolve().then(() => console.log(6)); // 🔵 Microtask - Queued
  Promise.reject().catch(() => console.log(7)); // 🔵 Microtask - Queued
  console.log(8);                    // 3️⃣ Synchronous - Output: 8
})();

Execution Flow:

1. All synchronous code runs: 1, 5, 8

2. All microtasks execute: 3, 6, 7 (in order they were queued)

3. Macrotasks execute: 2, 4 (in order they were queued)

Final Output:

1
5
8
3
6
7
2
4

Why This Order?

Microtasks vs Macrotasks

Microtasks (high priority):

Promise.then()
Promise.catch()
Promise.finally()
queueMicrotask()
MutationObserver

Macrotasks (lower priority):

setTimeout()
setInterval()
setImmediate() (Node.js)
I/O operations
UI rendering

Key Rules

1. All microtasks complete before any macrotask runs

2. Microtasks are processed until the microtask queue is empty

3. Macrotasks run one at a time, then check for microtasks again

Visual Representation

Call Stack
  ↓
Synchronous Code (1, 5, 8)
  ↓
Microtask Queue (3, 6, 7) ← Process ALL
  ↓
Macrotask Queue (2, 4) ← Process ONE, then check microtasks again

Common Pitfalls

Mistake 1: Thinking setTimeout(0) runs immediately

console.log(1);
setTimeout(() => console.log(2), 0);
console.log(3);
// Output: 1, 3, 2 (not 1, 2, 3!)

Even with 0ms delay, setTimeout is a macrotask and waits for all microtasks.

Mistake 2: Not understanding Promise rejection handling

Promise.reject().catch(() => console.log('caught'));
// This IS a microtask and executes immediately

.catch() creates a microtask, so it executes before macrotasks.

Mistake 3: Nested Promises

Promise.resolve()
  .then(() => {
    console.log(1);
    return Promise.resolve().then(() => console.log(2));
  })
  .then(() => console.log(3));
// Output: 1, 2, 3

Nested microtasks are added to the queue and processed in order.