The Microtask Queue

In addition to the standard Task Queue (which handles setTimeout, UI events, etc.), JavaScript maintains a second, higher-priority queue: the Microtask Queue.

Promises use the Microtask Queue to schedule their .then() and .catch() callbacks (or the resolutions of await).

The Rules of the Microtask Queue

The Microtask Queue operates under two strict rules:

  1. Absolute Priority: The Event Loop will execute all pending microtasks before it executes the very next standard task in the Task Queue.
  2. Self-Perpetuating: If a microtask schedules another microtask, that new microtask is appended to the end of the Microtask Queue and will still execute before the Event Loop moves on to the standard Task Queue.

Comparing the Queues

This code snippet clearly demonstrates the priority difference:

console.log("1. Main execution started");

// Scheduled in the standard Task Queue (Macrotask)
setTimeout(() => {
  console.log("4. Macrotask finished (setTimeout)");
}, 0);

// Scheduled in the Microtask Queue
Promise.resolve().then(() => {
  console.log("3. Microtask finished (Promise)");
});

console.log("2. Main execution ended");

Output Order:

  1. 1. Main execution started (Call Stack)
  2. 2. Main execution ended (Call Stack)
  3. 3. Microtask finished (Promise) (Microtask Queue gets priority)
  4. 4. Macrotask finished (setTimeout) (Task Queue goes last)

Do you need to care?

In everyday web development, you rarely need to deeply worry about the microtask queue. You can safely treat both Promises and setTimeout as “asynchronous operations that run later.” However, understanding this priority mechanism is vital for tracking down race conditions or performance bugs in complex applications.