queueMicrotask vs setTimeout
Microtask vs macrotask scheduling — queueMicrotask, Promise.then, setTimeout(0), and why microtasks can starve rendering.
- javascript
- queuemicrotask-vs
Both defer work until “later.” They use different queues, so order and UI impact differ. This is event-loop detail interviewers probe after the classic promise-vs-timeout question.
The rule
After a macrotask finishes (script, timer callback, many I/O events):
- Drain all microtasks (including ones queued while draining).
- Maybe render.
- Next macrotask (
setTimeout, etc.).
console.log('A');
setTimeout(() => console.log('D timeout'), 0);
queueMicrotask(() => console.log('C microtask'));
Promise.resolve().then(() => console.log('C2 promise'));
console.log('B');
// A, B, C microtask, C2 promise, D timeout
queueMicrotask(fn) and Promise.resolve().then(fn) are the same queue priority. Prefer queueMicrotask when you don’t need promise semantics.
setTimeout(0) is not “next line”
It’s “soon, as a macrotask,” after microtasks, often with a minimum delay clamp under nested timers. Use it to yield to the browser (paint, input) between chunks of work.
async function chunked(items, worker) {
for (let i = 0; i < items.length; i++) {
worker(items[i]);
if (i % 100 === 0) {
await new Promise((r) => setTimeout(r, 0)); // yield
}
}
}
Yielding only with queueMicrotask does not allow rendering — microtasks run before paint.
When to use which
| Need | Prefer |
|---|---|
| Run after current JS, before paint/timers | queueMicrotask / promise then |
| Let browser paint / handle events | setTimeout, MessageChannel, scheduler.yield |
| MutationObserver-style batching already microtask | don’t double-schedule blindly |
// Flatten async completion to microtask without a Promise library
function defer(fn) {
queueMicrotask(fn);
}
Starvation
function bomb() {
queueMicrotask(bomb);
}
bomb(); // page freezes — macrotasks never run
Microtask storms are real. Keep chains finite.
Node note
Node has additional phases (setImmediate, process.nextTick). process.nextTick is even more aggressive than microtasks in Node’s model. In browsers, stick to microtask vs timer story.
Interview answer (out loud)
“queueMicrotask schedules a microtask; setTimeout schedules a macrotask. After each task the engine drains microtasks before the next timer or render. That’s why microtasks run before setTimeout(0). I use microtasks for ‘after this sync code’ and timers/message channel when I need to yield for paint.”
Promise then vs queueMicrotask
queueMicrotask(() => console.log(1));
Promise.resolve().then(() => console.log(2));
queueMicrotask(() => console.log(3));
// 1, 2, 3 — FIFO microtask queue order
.then always creates a Promise reaction; queueMicrotask is lighter when you only need scheduling. Libraries that must run after DOM mutations often use microtasks (Vue’s nextTick history, Angular zones — different stacks, same idea).
MessageChannel as a macrotask yield
function postTask(fn) {
const { port1, port2 } = new MessageChannel();
port1.onmessage = () => fn();
port2.postMessage(null);
}
Faster than nested setTimeout(0) clamps in some browsers; still a task boundary that allows paint — unlike microtasks.
Debugging order bugs
When “this ran before paint but after my promise,” log with labels and remember: sync → microtasks → (render opportunity) → next task. Mixing await (microtask resume) with timers is the usual story.
Further reading
Related
Related guides
- AbortControllerCancel fetch and other async work with AbortController and AbortSignal — timeouts, race conditions, and cleanup when components unmount.
- Array find, some, every, includesShort-circuiting array predicates: find, findIndex, some, every, and includes — when to use each and common interview traps.
- Array map, filter, reducemap, filter, and reduce as the core transform toolkit — immutability, chaining costs, reduce patterns, and when a plain loop is clearer.
- Arrow Functions Deep DiveLexical this, no arguments object, no construct, concise bodies — when arrows help and when methods and generators need classic functions.
- Async Iteration and for await...ofAsync iterables, for await...of, and streaming data — how async iterators differ from Promise.all and when to use each.