Q26 of 40 · JavaScript

What is the difference between microtasks and macrotasks in the JavaScript event loop?

JavaScriptMidjavascriptevent-loopmicrotasksmacrotaskspromisesasync

Short answer

Short answer: Microtasks (Promise callbacks, queueMicrotask) run immediately after the current task and drain completely before the next macrotask. Macrotasks (setTimeout, setInterval, I/O) are scheduled once per event loop iteration. This ordering means Promises always resolve before the next timer fires.

Detail

The event loop processes tasks from two queues with different priorities.

Macrotask queue (task queue): One task is dequeued per event loop iteration. Sources include: setTimeout, setInterval, setImmediate (Node), I/O callbacks, UI rendering.

Microtask queue: After each macrotask completes (and after the current synchronous script if it's the first iteration), the event loop drains the entire microtask queue before picking the next macrotask. Sources include: Promise.then/catch/finally, queueMicrotask, MutationObserver callbacks, async function continuations after await.

Practical consequence: If a Promise.then callback schedules another Promise.then, it runs before any setTimeout — the microtask queue re-drains after every microtask. This can starve the macrotask queue if microtasks enqueue indefinitely.

In test debugging: Knowing this priority explains why await Promise.resolve() flushes all pending Promise callbacks — a useful technique in tests to let all microtasks settle before asserting.

// EXAMPLE

console.log("1 sync");

setTimeout(() => console.log("5 setTimeout"), 0); // macrotask

Promise.resolve()
  .then(() => {
    console.log("3 microtask 1");
    return Promise.resolve();
  })
  .then(() => console.log("4 microtask 2")); // queued after microtask 1

console.log("2 sync end");

// Output: 1, 2, 3, 4, 5
// All microtasks drain before setTimeout fires

// Test helper: flush microtasks
async function flushMicrotasks() {
  await Promise.resolve(); // yields to drain microtask queue
}

// WHAT INTERVIEWERS LOOK FOR

The two-queue model and that microtasks drain completely before the next macrotask. Knowing that Promise chains all run as microtasks. Bonus: the `await Promise.resolve()` flush pattern for tests.

// COMMON PITFALL

Treating setTimeout(fn, 0) as immediate — it is a macrotask, so any pending Promise callbacks run first. This is a source of unexpected ordering bugs.