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:
- Call Stack – where synchronous code runs
- Microtask Queue – where high-priority async tasks wait
- Macrotask Queue – where scheduled I/O and timers wait
The event loop executes:
- All synchronous code
- Then all microtasks
- 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
| Feature | Microtask Queue | Macrotask Queue |
|---|---|---|
| Executed When | After current stack, before next event | After microtasks and rendering |
| Examples | Promise.then(), queueMicrotask() | setTimeout(), setInterval(), I/O |
| Priority | Higher | Lower |
| Processing Order | Runs all microtasks in a batch | One 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: synchronousC: 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 usingqueueMicrotask()
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
| Language | Microtask Equivalent |
|---|---|
| JavaScript | Promise.then(), queueMicrotask() |
| Node.js | process.nextTick() + Promises |
| Python | asyncio ready queue (coroutines) |
| Rust | Futures polled in an event loop |
| Swift | Structured 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









