Microtasks in JavaScript
Microtasks are small tasks that run right after the current code (the current macrotask) and before the next macrotask from the queue begins. They form a separate structure, the Microtask Queue.
Theory
TL;DR
- A microtask is a callback the Event Loop runs after the current code but before any macrotask.
- The main sources are
Promise.then,Promise.catch,queueMicrotaskandMutationObserver. - The microtask queue is drained completely, while only one task is taken from the macrotask queue per turn.
- Microtasks have higher priority, so
Promise.thenalways beatssetTimeout(..., 0). - Spawning microtasks endlessly "freezes" the Event Loop: macrotasks and rendering never get control.
- The order is: the stack, all microtasks, the next macrotask.
Quick example
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');The resulting output order:
1
4
3
2The key idea
JavaScript has two levels of tasks:
| Type | Examples | When it runs |
|---|---|---|
| Macrotasks (Tasks) | setTimeout, setInterval, DOM events, network callbacks | After the stack is cleared |
| Microtasks | Promise.then, Promise.catch, queueMicrotask, MutationObserver | Right after the current task, before the next macrotask |
In other words, after the current code (a function, for example) has finished, the Event Loop first checks the microtask queue. If there is anything in it, all of those tasks run one after another until the queue is empty.
Walking through the example
console.log('1')runs immediately. Output:1setTimeout(..., 0): the callback lands in the macrotask queue (task queue).Promise.then(...): the callback lands in the microtask queue.console.log('4')runs immediately. Output:4- The current stack is empty, so all microtasks run, that is
console.log('3'). - Only after that do macrotasks run, that is
console.log('2').
What creates microtasks
| Source | Example |
|---|---|
| Promise | Promise.resolve().then(...) |
| queueMicrotask() | queueMicrotask(() => { ... }) |
| MutationObserver | Watching DOM changes |
async/await belongs here too: the continuation after an await is scheduled as a microtask.
Microtasks vs macrotasks
| Property | Microtasks | Macrotasks |
|---|---|---|
| Queue | Microtask Queue | Task Queue |
| Examples | Promise.then, queueMicrotask | setTimeout, setInterval, click |
| Priority | Higher, they run earlier | Lower |
| When they run | After the current code, but before the next event | After all microtasks |
Be careful with microtasks
If you keep adding new microtasks inside then forever, the engine never reaches the next macrotask and you end up with a "frozen" Event Loop:
function loop() {
Promise.resolve().then(loop);
}
loop(); // the browser will hangThe stack does not overflow here, because every microtask completes properly. The microtask queue simply never becomes empty, so timers, event handlers and page repaints never get their turn.
Terms at a glance
| Term | Description |
|---|---|
| Microtask | A small task that runs after the current code and before the next macrotask |
| Microtask queue | Holds Promise.then, queueMicrotask and similar callbacks |
| Priority | Higher than that of macrotasks |
| Event Loop order | The stack, then all microtasks, then the next macrotask |
Common mistakes
- Treating
Promise.thenandsetTimeout(..., 0)as equals. A promise is a microtask and will always run first, no matter the order in the code. - Thinking a microtask is just a "faster timer". It is a different queue with a different priority, not a smaller delay.
- Scheduling microtasks recursively for background work. That blocks rendering; use
setTimeoutorrequestIdleCallbackfor chunked work. - Forgetting that the continuation after
awaitis a microtask too. That is why code afterawaitruns later than people expect, yet still before any timer. - Confusing the microtask queue with the call stack. Microtasks only run once the stack is already empty.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.