# 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.)

## 1. The 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](diagrams/01-priority.svg)

## 2. What Goes Where

| Microtask sources | Macrotask sources |
|---|---|
| `Promise.prototype.then/catch/finally` callbacks | `setTimeout` / `setInterval` |
| `queueMicrotask(fn)` | `setImmediate` (Node.js only) |
| `async function` code after an `await` | I/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) |

## 3. Side-by-Side Comparison

| | Microtask | Macrotask |
|---|---|---|
| Priority | higher — always drained first | lower — one runs per loop iteration |
| Queue draining | **entire** queue drained per pass, including new entries added during draining | **one** task per pass, then microtasks are re-checked |
| Typical use | Promise resolution, reacting quickly to already-available data | timers, 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 |

## 4. Direct 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.

## 5. `queueMicrotask` — 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`.

## 6. `async`/`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 `await`ing even an already-resolved value still yields to the microtask queue — the code after `await` never runs perfectly synchronously, even for instantly-available values.

## 7. Node.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 Hood: The 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.

## 8. 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.

## 9. Quick Reference

```
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
```
