The Microtask Queue
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.thenPromise.prototype.catchPromise.prototype.finallyqueueMicrotask- 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
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.
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.
Avoid recursive microtask starvation
This shape can keep the runtime busy with microtasks and delay timers. Use task-based yielding for large workloads.
Playground
Press Run to execute the code and see output here.
Output Prediction
Predict the output #1
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
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
EasyImplement 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.
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.