Microtask Queue: Promises & queueMicrotask

Med

The Microtask Queue is a special queue for high-priority asynchronous operations. Promises (.then, .catch, .finally), queueMicrotask(), and MutationObserver use this queue. The critical difference from macrotasks: the event loop drains the ENTIRE microtask queue before running any macrotask. This means microtasks always execute before the next macrotask, even if queued later.

Interactive Visualization

Microtask Queue
(empty)
Macrotask Queue
(empty)
CodeSync
1console.log('1');
2
3setTimeout(() => console.log('timeout'), 0);
4
5Promise.resolve()
6 .then(() => console.log('promise'));
7
8console.log('2');
Output
1
Script starts executing synchronously.
1 / 7
Key Insight: Microtasks ALWAYS run before macrotasks. setTimeout(0) means "next macrotask tick", not "immediately"!

Key Points

  • Microtasks: Promises, queueMicrotask, MutationObserver
  • Event loop drains ALL microtasks before next macrotask
  • Microtasks can queue more microtasks (drained recursively)
  • Always execute before next macrotask

Code Examples

Promise vs setTimeout Priority

console.log("1");

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

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

console.log("4");

// Output: 1, 4, 3, 2

// Why 3 before 2?
// 1. Sync: 1, 4
// 2. Promise queues microtask
// 3. setTimeout queues macrotask
// 4. Event loop: Run ALL microtasks (3)
// 5. Then run one macrotask (2)

Microtasks (Promises) run before macrotasks (setTimeout), regardless of order queued.

Chained Promises Queue Multiple Microtasks

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

setTimeout(() => console.log("3"), 0);

console.log("0");

// Output: 0, 1, 2, 3

// Each .then() queues a microtask
// All microtasks drain before setTimeout

Each Promise .then() queues a separate microtask. All run before any macrotask.

Microtask Starvation

// DANGER: Infinite microtasks block everything
function infiniteMicrotasks() {
  Promise.resolve().then(() => {
    console.log("Microtask");
    infiniteMicrotasks(); // Queue another
  });
}

// infiniteMicrotasks();
// setTimeout(() => console.log("Never runs"), 0);

// The microtask queue never empties
// Macrotasks are starved forever

Recursively queueing microtasks prevents macrotasks from ever running.

Common Mistakes

  • Thinking Promise.then() is like setTimeout
  • Not knowing microtasks run before macrotasks
  • Creating infinite microtask loops

Interview Tips

  • Know the order: Sync → All Microtasks → One Macrotask
  • Explain why Promise is faster than setTimeout
  • Know that microtasks can starve macrotasks
  • List microtask sources: Promises, queueMicrotask