Introduction

The Microtask Queue is a crucial component of modern JavaScript runtimes and asynchronous programming models. It plays a key role in determining execution order when handling promises, mutations, and other asynchronous operations. Understanding how the microtask queue works — and how it differs from the macrotask queue — is essential for writing predictable, performant, and bug-free asynchronous code.

Microtasks are tiny, prioritized tasks that are executed after the current synchronous code but before any rendering or other queued events. In JavaScript, the Promise.then(), MutationObserver, and queueMicrotask() APIs all schedule microtasks.

The JavaScript Event Loop at a Glance

To understand microtasks, we must understand the event loop. JavaScript (in browsers and Node.js) is single-threaded, and it uses an event loop to manage execution in stages:

  1. Call Stack – where synchronous code runs
  2. Microtask Queue – where high-priority async tasks wait
  3. Macrotask Queue – where scheduled I/O and timers wait

The event loop executes:

  1. All synchronous code
  2. Then all microtasks
  3. Then one macrotask, and repeats

What Is a Microtask?

A microtask is a short function scheduled to run after the current script completes, but before any rendering, setTimeouts, or I/O events.

Common Sources of Microtasks in JavaScript:

  • Promise.then(), catch(), finally()
  • queueMicrotask()
  • MutationObserver

Example:

console.log("script start");

Promise.resolve().then(() => {
  console.log("microtask 1");
});

console.log("script end");

Output:

script start
script end
microtask 1

The promise callback is deferred until after synchronous code — but before any macrotasks like setTimeout.

Microtasks vs Macrotasks

FeatureMicrotask QueueMacrotask Queue
Executed WhenAfter current stack, before next eventAfter microtasks and rendering
ExamplesPromise.then(), queueMicrotask()setTimeout(), setInterval(), I/O
PriorityHigherLower
Processing OrderRuns all microtasks in a batchOne macrotask per loop iteration

Visual Timeline:

[Synchronous Code]
→ Microtasks (flush all)
→ Macrotask (e.g., setTimeout)
→ Microtasks (if scheduled inside macrotask)
→ Next Macrotask

Practical Example with setTimeout

console.log("A");

setTimeout(() => {
  console.log("B");
}, 0);

Promise.resolve().then(() => {
  console.log("C");
});

console.log("D");

Output:

A
D
C
B
  • A, D: synchronous
  • C: microtask (Promise)
  • B: macrotask (setTimeout)

queueMicrotask()

This API lets you explicitly schedule a microtask:

queueMicrotask(() => {
  console.log("This runs after sync, before timeout");
});

Equivalent to:

Promise.resolve().then(() => {
  console.log("Microtask via promise");
});

Use case: When you want something deferred, but don’t want to wait until the next macrotask.

Nested Microtasks

Microtasks can schedule more microtasks — and the loop will keep flushing until the queue is empty:

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

Output:

microtask 1
microtask 2

The second microtask is enqueued during microtask processing, but still runs before any macrotask is considered.

Browser Internals

In browser runtimes:

  • After all synchronous script executes,
  • The microtask queue is flushed,
  • Then the UI can render,
  • Then macrotasks (like setTimeout) can run.

This means microtasks can delay UI updates:

document.body.style.background = "red";

Promise.resolve().then(() => {
  while (true) {}  // blocks rendering
});

Since microtasks block rendering, abuse of the queue can hurt performance.

Microtasks in Node.js

Node.js uses a similar system, with some differences:

  • Microtasks: process.nextTick(), Promise.then()
  • Macrotasks: setTimeout(), setImmediate(), I/O

Priority:

In Node.js, process.nextTick() is even higher priority than Promises:

process.nextTick(() => {
  console.log("tick");
});

Promise.resolve().then(() => {
  console.log("promise");
});

Output:

tick
promise

Because nextTick runs before the microtask queue from Promises.

Microtask Queues in Python (asyncio)

In Python’s asyncio, microtasks don’t exist by name, but the event loop works similarly. Awaited coroutines are scheduled in an internal ready queue that behaves like a microtask queue.

import asyncio

async def main():
    print("Start")
    asyncio.create_task(asyncio.sleep(0))
    print("End")

asyncio.run(main())

The sleep(0) yields to the event loop, but will run only after the current task finishes.

Microtask-Driven Bugs

1. Starvation

Because the microtask queue is flushed entirely before moving to the next macrotask, it can cause starvation if new microtasks are continuously enqueued.

function loop() {
  Promise.resolve().then(loop);
}

loop();  // never yields to macrotask queue or rendering

2. Order Confusion

setTimeout(() => console.log("timeout"), 0);
queueMicrotask(() => console.log("microtask"));

Even though both are “async”, the microtask always wins.

Debugging Microtasks

  • Use browser dev tools async stack traces
  • Log markers (console.log("microtask start"))
  • Be careful when combining multiple layers of abstraction (e.g., Promises + Observables)

Real-World Use Cases

  • Promise-based APIs: fetch, DB requests
  • Framework internals: React batching uses microtasks
  • Efficient UIs: defer DOM updates without delaying interaction
  • Polyfills: APIs like setImmediate() can be mimicked using queueMicrotask()

Best Practices

  • Use microtasks for logic that must run before UI/rendering
  • Avoid large computations in microtasks (they block rendering)
  • Be cautious of recursive microtask scheduling (starvation)
  • Don’t mix macrotask and microtask-based APIs carelessly

Microtasks in Other Languages

LanguageMicrotask Equivalent
JavaScriptPromise.then(), queueMicrotask()
Node.jsprocess.nextTick() + Promises
Pythonasyncio ready queue (coroutines)
RustFutures polled in an event loop
SwiftStructured concurrency queues

While names vary, the concept of deferring lightweight tasks until after current execution but before I/O exists in all modern event-loop-based systems.

Conclusion

The Microtask Queue is a fundamental part of asynchronous execution, particularly in JavaScript. It provides a prioritized channel for promise callbacks and other low-latency operations that should occur before rendering or handling I/O.

Mastering the behavior of microtasks — including their ordering, interaction with synchronous and macrotask logic, and performance implications — helps you write cleaner, more predictable, and more performant asynchronous code.

If your async code ever feels “out of order” or UI changes lag unexpectedly, understanding the microtask queue might just solve the mystery.

Related Keywords

  • Async Function
  • Asynchronous IO
  • Callback Queue
  • Event Loop
  • JavaScript Runtime
  • Macrotask Queue
  • Microtask Scheduler
  • Mutation Observer
  • Promise Resolution
  • Process Next Tick
  • queueMicrotask Function
  • Script Execution Order
  • Synchronous Code
  • Task Scheduling
  • Zero Delay Timeout