The Microtask Queue And Priority

Not all queues are created equal. JavaScript has a VIP lane for Promise callbacks called the Microtask Queue.

April 25, 20263 min read4 / 5

We've seen two ways of getting background work back into JavaScript: the Callback Queue for old-school facades and the Promise object for new ones. But what happens if they both try to enter the Call Stack at the same time?

The Essentials

  1. Microtask Queue: A special high-priority queue specifically for functions deferred by Promises (via .then).
  2. Callback Queue (Task Queue): The standard queue for regular browser APIs like setTimeout and DOM events.
  3. The Priority Loop: The Event Loop always checks the Microtask Queue before the Callback Queue.
  4. Starvation: If you keep adding to the Microtask Queue, the Callback Queue might never get a turn (though this is rare in practice).

The Great Race

Let's look at a complex scenario that illustrates the rules of the road:

JavaScript
function display(data) { console.log(data); } function printHello() { console.log("Hello"); } function blockFor300ms() { /* long loop */ } setTimeout(printHello, 0); const futureData = fetch('tiktok.com/will'); futureData.then(display); blockFor300ms(); console.log("Me first!");

The Timeline

  1. 0ms: setTimeout triggers a 0ms timer. It completes immediately. printHello is pushed into the Callback Queue.
  2. 1ms: fetch triggers a network request. A Promise object is returned. display is attached to its onFulfilled array.
  3. 2ms: blockFor300ms begins. The thread is now blocked.
  4. 270ms: The network request returns "cute puppy".
    • futureData.value is updated.
    • display is pushed into the Microtask Queue.
  5. 302ms: blockFor300ms finishes. Global code continues to log "Me first!".
  6. 303ms: The Call Stack is finally empty. The Event Loop kicks in.

The Decision

The Event Loop has a choice. printHello has been waiting in the Callback Queue since 0ms. display only arrived in the Microtask Queue at 270ms.

By all laws of fairness, printHello should go first. But JavaScript is not fair.

The Event Loop checks the Microtask Queue first. It finds display, dequeues it, and pushes it onto the Call Stack. "cute puppy" is logged.

Only after the Microtask Queue is completely empty does the Event Loop look at the Callback Queue. Finally, printHello gets its turn. "Hello" is logged last.

JavaScript Execution Engine
Thread of Execution
1setTimeout(printHello, 0);
2fetch(...).then(display);
3blockFor300ms();
4console.log("Me first!");

Step 1:At 270ms, the fetch completes. display enters the MICROTASK QUEUE. It is now waiting alongside printHello (who is in the Callback Queue).

Memory
CB_QueueprintHello
Call Stack
Global
blockFor300ms()
Bottom of Stack

Why the Priority?

Promises are usually associated with vital data and state changes. By giving them their own priority queue, JavaScript ensures that our application logic (like updating the UI with fetched data) happens as fast as possible, even if the Callback Queue is clogged with dozens of small timers or DOM events.

Now that we understand how the data gets in, what happens when things go wrong? In the final part, we'll look at Error Handling and the ability to Abort our background work.

Further Reading and Watching