Engineering Notes
Home/Languages/JavaScript/Microtasks vs Macrotasks
Languages · JavaScript

Microtasks vs Macrotasks

(The Event Loop topic covers how these queues are processed; this topic catalogs what goes in each queue and compares them directly.)

💻 Languages● Advanced⏱ 5 min read🛠️ 3 exercises🎤 5 interview Q&As

🎯 What you'll learn

  • The Core Rule
  • What Goes Where
  • Side-by-Side Comparison
  • Direct Comparison Example
  • queueMicrotask — Scheduling a Microtask Directly

(The Event Loop topic covers how these queues are processed; this topic catalogs what goes in each queue and compares them directly.)

1The Core Rule

All pending microtasks run before the next macrotask, every single time — no exceptions. This one rule explains almost every "surprising" async ordering question.

Sync code runs, then ALL microtasks drain, then exactly one macrotask runs, then microtasks drain again
Sync code runs, then ALL microtasks drain, then exactly one macrotask runs, then microtasks drain again

2What Goes Where

Microtask sourcesMacrotask sources
Promise.prototype.then/catch/finally callbackssetTimeout / setInterval
queueMicrotask(fn)setImmediate (Node.js only)
async function code after an awaitI/O callbacks (fs.readFile, network events)
MutationObserver callbacks (browser)UI events (click, keydown)
requestAnimationFrame (browser — technically its own category, but runs after microtasks, before the next paint)

3Side-by-Side Comparison

MicrotaskMacrotask
Priorityhigher — always drained firstlower — one runs per loop iteration
Queue drainingentire queue drained per pass, including new entries added during drainingone task per pass, then microtasks are re-checked
Typical usePromise resolution, reacting quickly to already-available datatimers, scheduling work for "later," I/O completion
Can starve the other?Yes — an infinite microtask chain can starve all macrotasks (see the Event Loop topic)No — a macrotask cannot block microtasks from running; each macrotask still yields back to microtask draining afterward

4Direct Comparison Example

js
console.log("1");

setTimeout(() => console.log("2 (macrotask)"), 0);
setTimeout(() => console.log("3 (macrotask)"), 0);

Promise.resolve().then(() => console.log("4 (microtask)"));
Promise.resolve().then(() => console.log("5 (microtask)"));

console.log("6");

Output: 1, 6, 4, 5, 2, 3 — both microtasks run before either macrotask, regardless of registration order between the two categories.

5queueMicrotask — Scheduling a Microtask Directly

js
console.log("A");
queueMicrotask(() => console.log("B (explicit microtask)"));
console.log("C");
// A, C, B

Useful when you want to defer work just slightly — past the current synchronous code — without the extra overhead or minimum-delay behavior of setTimeout.

6async/await Uses Microtasks Internally

js
async function example() {
  console.log("1: sync part of async function");
  await null; // ANYTHING after this point is scheduled as a microtask
  console.log("3: resumed as a microtask");
}
example();
console.log("2: continues synchronously after calling example()");
// 1, 2, 3

This is why awaiting even an already-resolved value still yields to the microtask queue — the code after await never runs perfectly synchronously, even for instantly-available values.

7Node.js Extra Detail: process.nextTick

Node.js has an additional queue, process.nextTick, that runs with even higher priority than Promise microtasks — it's drained completely before the microtask queue on each pass. This is Node-specific and does not exist in browsers.

js
Promise.resolve().then(() => console.log("promise microtask"));
process.nextTick(() => console.log("nextTick"));
// nextTick, then promise microtask — in Node.js only
Under the HoodThe Microtask Checkpoint Is a Real, Callable Operation▾

"Draining the microtask queue" isn't just a conceptual step in a diagram — it corresponds to a concrete operation engines expose internally, called a microtask checkpoint (V8's embedder API calls this PerformMicrotaskCheckpoint). Whatever is embedding the JS engine — a browser, Node — is responsible for calling this checkpoint at the right moments; V8 itself does not decide on its own when to run it.

Who calls the checkpoint, and when

  • Node.js: the checkpoint runs after each callback invocation and between each of the six libuv phases described in the Event Loop topic's "Under the Hood" section — this is precisely why microtasks interleave as tightly as they do with Node's phase structure.
  • Browsers: the checkpoint runs after each task completes, and additionally at specific points during certain DOM operations — which is why microtasks can sometimes run mid-way through, e.g., a sequence of synchronous DOM mutations, not only at the very end of a script.

Why this architecture matters: microtasks are an engine-level queue, macrotasks are host-defined

setTimeout, setInterval, and I/O callbacks are not part of the JavaScript language specification at all — they're provided by the host environment (the browser's Web APIs, or Node's libuv bindings), which is why their exact scheduling mechanics (timer heaps, libuv phases) differ between environments. The microtask queue, by contrast, genuinely is part of the ECMAScript specification itself (HostEnqueuePromiseJob notwithstanding, the ordering guarantees around draining are spec-mandated) — this is why microtask-vs-macrotask priority behaves identically across every conformant JS environment, while the fine details of macrotask scheduling (libuv's six phases vs a browser's task queue) genuinely differ by host.

⚠️ Common Pitfalls

  1. Assuming setTimeout(fn, 0) is "as fast as possible" — a Promise microtask is always faster; use microtasks for genuinely urgent follow-up work.
  2. Forgetting await always defers, even for synchronous-looking values — await 5 still yields to the microtask queue before continuing.
  3. Overusing process.nextTick in Node — because it preempts even Promise microtasks, heavy use can starve Promise resolution, similar to the microtask-starves-macrotask problem in the Event Loop topic.
  4. Assuming requestAnimationFrame behaves like a microtask — it's scheduled relative to the browser's paint cycle, not the microtask/macrotask split; treat it as its own category.

📋 Quick Reference

text
microtask: Promise .then/.catch/.finally, queueMicrotask, code after await
macrotask: setTimeout, setInterval, setImmediate (Node), I/O, UI events
priority:  ALL microtasks drain before the NEXT macrotask, every pass
Node only: process.nextTick > Promise microtasks > macrotasks

🎤 Interview Q&A

Questions interviewers ask about this topic. Answer out loud first, then open a card to compare.

Q1What are microtasks and macrotasks?core▾

Macrotasks (tasks): script execution, setTimeout/setInterval, I/O, UI events, MessageChannel. Microtasks: promise reactions, queueMicrotask, MutationObserver (and process.nextTick in Node, with higher priority). All microtasks run after each task before the next task.

Follow-ups interviewers ask
  • Which runs first after sync code? Microtasks.
Q2Predict: setTimeout(() => log('t')); Promise.resolve().then(() => log('p1')).then(() => log('p2')); queueMicrotask(() => log('q')).core▾

p1, q, p2, t: p1 and q are queued in order; p2 is queued when p1 runs; the timer runs after all microtasks.

Follow-ups interviewers ask
  • Why is p2 after q? It's queued only after p1 executes.
Q3Where does await resume?core▾

The continuation after await is scheduled as a microtask when the awaited promise settles, so code after await runs after the current synchronous code.

Follow-ups interviewers ask
  • Does await on a non-promise yield? Yes, it still defers to a microtask.
Q4When would you use queueMicrotask?senior▾

To defer work until after the current operation but before rendering or other tasks, e.g. batching notifications in a library while keeping consistent state.

Follow-ups interviewers ask
  • Risk? Starving rendering if overused.
Q5How does process.nextTick relate in Node?core▾

Node drains the nextTick queue before the promise microtask queue; excessive nextTick recursion can starve I/O.

Follow-ups interviewers ask
  • Guidance? Prefer queueMicrotask/setImmediate in new code.

🛠️ Practice exercises

Each exercise is a file in this topic folder. Code exercises are self-checking: run them and make the checks pass.