JS Sketchbooksee JavaScript think ✏️
← back to phase 6

6.5 — Microtasks vs macrotasks

The 6.2 machine had one queue. Small confession: it has two — and the second one has priority boarding. Promise reactions don't wait in the regular callback queue with the timers and clicks; they ride the microtask queue — the express lane.

The rule that decides every ordering puzzle: when the stack empties, the engine drains the entire microtask queue first — every promise reaction, including ones queued by other reactions — and only then moves ONE macrotask (timer, click). This is the final piece of the event-loop machine, and the snippet on the left is the exact one interviewers use to check who really owns it.

watch it happen
console.log("1 sync");

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

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

console.log("2 sync");

Vocabulary first. MACROTASKS: the 6.2 queue — setTimeout callbacks, clicks, network events.

call stackglobal — running⚡ microtasks — drains COMPLETELY🐢 macrotasks — one per turnfirst,all of itthen oneMACROTASKS: the 6.2 queue — setTimeout callbacks, clicks, network events1 sync
under the hood

The deeper story, with the real names for things — this part is what turns “I saw it” into “I can explain it.”

Who rides where — the practical table: micro = promise reactions, queueMicrotask(fn), await-resumptions (next lesson!). Macro = setTimeout/setInterval, DOM events, network callbacks.

The drain point is precisely "each time the stack empties" — even between two macrotasks, the micro lane gets fully cleared.

Why does the language bother? Consistency guarantees. A promise reaction is guaranteed to run before anything else can happen — no click, no timer can sneak between your .thens. Chained state updates feel atomic (indivisible). The guarantee is exactly what await builds on — 6.6 will read as "microtasks with pretty syntax."

Debug-life note: this is why a log inside .then appears before a log inside a setTimeout(fn, 0) written earlier. That ordering "bug" has consumed thousands of confused hours. You now dissolve it with one sentence: different lanes.

your turn

⌨️ one of each, in the right lanes

Produce the exact output sync → micro → macro using one plain print, one promise reaction, and one zero-delay timer — written in an order that PROVES the lanes decide, not the line numbers.

requirements:

  • Schedule the timer FIRST in your code: a 0ms setTimeout printing "macro".
  • Then attach a promise reaction printing "micro" (an already-resolved promise is fine).
  • Then a plain console.log("sync") as your LAST line. Required output: sync, micro, macro — the reverse of how you wrote them.

when you press RUN, the console must show exactly:

sync
micro
macro

✏️ Quick check 1

Type the FULL output order, separated by spaces:

setTimeout(() => console.log("t"), 0);
Promise.resolve().then(() => console.log("p"));
console.log("s");

✏️ Quick check 2

When the stack empties, the microtask queue drains ___ before the next macrotask. Type: partially or completely.

✏️ Quick check 3

A .then reaction and a setTimeout(fn, 0) are both ready. Which runs first? Type: then or timeout.

teach it back

🗣️ Now teach it back

The interviewer shows you sync + setTimeout(0) + Promise.then and asks for the output order and WHY. Give the two-queue explanation, the drain rule, and name what rides in each lane.

Write it as if your friend is sitting next to you. Saved to your journal — future-you will use these notes to teach others.

a few sentences, minimum — you’ve got this
to remember
Two queues, one stack: macro (timers, clicks, network) and micro (promise reactions, queueMicrotask, await-resumptions).
THE rule: stack empties → drain ALL microtasks (even newly queued ones) → then ONE macrotask.
Therefore: sync → micro → macro. Promise reactions always beat setTimeout(fn, 0) — the interview snippet is now arithmetic.
next: 6.6 async/await →