Compile Ready
Module 8 · Asynchronous JavaScript

The Microtask Queue

Advanced13m read18m practice31m total
MicrotasksPromisesqueueMicrotaskStarvationOutput Prediction

Introduction

The microtask queue is where promise reactions and queueMicrotask callbacks wait. It has higher priority than the task queue: after the current stack clears, the runtime drains every queued microtask before the next timer or event task.

This queue is the secret behind most async ordering puzzles.

Why This Matters

Many candidates know that promises are async, but fewer can explain how async they are. Promise handlers do not run synchronously when a promise resolves, but they also do not wait for the next timer task. They run at the microtask checkpoint.

Theory

Sources of microtasks

Common microtask sources are:

  • Promise.prototype.then
  • Promise.prototype.catch
  • Promise.prototype.finally
  • queueMicrotask
  • MutationObserver callbacks in browsers

The callback passed to the Promise constructor is not a microtask. The executor runs synchronously. The reactions registered with .then, .catch, and .finally are microtasks.

Drain-until-empty semantics

At a microtask checkpoint, the runtime does not run just one microtask. It repeatedly dequeues and runs microtasks until the queue is empty. If a microtask enqueues another microtask, the new one is added to the end and still runs before the next task.

FIFO ordering

For ordinary promise and queueMicrotask examples, microtasks run in the order they are queued. Promise chaining can create new microtasks later. For example, the second .then in a chain cannot run until the promise returned by the first .then settles, so another already queued microtask may run between the two.

Starvation risk

A recursive microtask loop can prevent timers and rendering from happening. That makes microtasks powerful but dangerous for heavy work. Use them for small follow-up actions that must happen before the next task, not for long-running loops.

Interview checklist

When solving output predictions, mark each line as one of three categories: synchronous now, microtask later in this turn, or task in a future turn. Then drain all microtasks before touching tasks.

Visual Diagrams

Microtask checkpoint

Current task completes
        |
        v
Microtask queue:
  1. promise then A
  2. queueMicrotask B
        |
        v
Run A
  A queues C
        |
        v
Queue now:
  1. B
  2. C
        |
        v
Run B, then C, then next task

New microtasks are appended and still drain before any task callback.

Promise chain interleaving

Initial queue:
  first then of chain
  independent then

Run first then:
  logs A
  resolves returned promise
  queues second then of chain

Queue:
  independent then
  second then of chain

A promise chain can be interleaved with other already queued microtasks.

Code Examples

Promise executor is synchronous; reaction is microtask

A very common interview trap: creating a promise runs the executor immediately.

Loading…

Avoid recursive microtask starvation

This shape can keep the runtime busy with microtasks and delay timers. Use task-based yielding for large workloads.

Loading…

Playground

Loading editor…
Console

Press Run to execute the code and see output here.

Output Prediction

Predict the output #1

javascript
1
2console.log('start');
3
4queueMicrotask(function () {
5 console.log('microtask 1');
6
7 queueMicrotask(function () {
8 console.log('microtask 3');
9 });
10});
11
12Promise.resolve().then(function () {
13 console.log('microtask 2');
14});
15
16setTimeout(function () {
17 console.log('timer');
18}, 0);
19
20console.log('end');

Predict the output #2

javascript
1
2Promise.resolve()
3 .then(function () {
4 console.log('A');
5 })
6 .then(function () {
7 console.log('B');
8 });
9
10Promise.resolve().then(function () {
11 console.log('C');
12});
13
14console.log('D');

Coding Exercises

Schedule after the current stack

Easy

Implement afterCurrentTurn(callback) so callback runs after the current synchronous code but before a setTimeout(..., 0) task.

Interview Questions

1What goes into the microtask queue?

Promise reactions from then, catch, and finally, callbacks passed to queueMicrotask, and browser features such as MutationObserver. The promise executor itself is synchronous and does not go into the microtask queue.

Asked at:GoogleUberMicrosoft
2Can microtasks starve the task queue?

Yes. Because the runtime drains microtasks until the queue is empty before running the next task, a microtask that continuously queues more microtasks can delay timers, input handling, and rendering indefinitely.

Follow-ups

  • When would you choose a task instead of a microtask?
  • Why can promise chains delay rendering?

Quiz

1. Which statement about promise executors is true?

2. If a microtask schedules another microtask, when does the new microtask run?

Summary

  • Promise reactions and `queueMicrotask` callbacks are microtasks.
  • Promise executors run synchronously; `.then` handlers run later.
  • The microtask queue drains until empty before the next task.
  • Recursive microtasks can starve timers, input, and rendering.

Cheat Sheet

Microtask sources: Promise.then, catch, finally, queueMicrotask, MutationObserver.

Not a microtask: the new Promise executor.

Drain rule: run all queued microtasks; newly queued ones are appended and also run before the next task.

Puzzle tactic: sync first, then FIFO microtasks, then tasks.