JavaScript Event Loop: Microtasks vs Macrotasks (Interview-Ready)

A practical, interview-focused guide to the JS event loop: call stack, Web APIs, task queues, microtasks vs macrotasks, and common trick questions with real examples.

F

Frontend Interview Team

February 08, 2026

~10 min
JavaScript Event Loop: Microtasks vs Macrotasks (Interview-Ready)

If you’re interviewing for frontend roles, the event loop is one of the most common “tell me how JavaScript works” topics.

You don’t need a super-academic explanation. You need a clear mental model and the ability to reason about output order.

30‑second interview answer

JavaScript runs synchronous code on the call stack. Async work (timers, network, DOM events) is handled by the host environment and queued as tasks. After each task finishes, the event loop drains the microtask queue (Promise callbacks) before running the next macrotask (like setTimeout). That’s why promises usually run before timers.

Key points (remember these)

  • Microtasks (Promise.then, queueMicrotask) run before the next macrotask.
  • Macrotasks (setTimeout, I/O) run one per turn of the loop.
  • “Re-render order” questions are really “queue priority” questions.

The 5 pieces you must know

  1. Call Stack
  • Where synchronous JS runs.
  • One function at a time.
  1. Web APIs / Host APIs (browser or Node)
  • Timers (setTimeout), network, DOM events, etc.
  • These are not the JS engine itself.
  1. Task Queue (Macrotask Queue)
  • setTimeout, setInterval, I/O, message channels, etc.
  1. Microtask Queue
  • Promise callbacks: .then/.catch/.finally
  • queueMicrotask
  • (In browsers) MutationObserver
  1. Event Loop
  • The coordinator.
  • It picks what runs next.

The rule that wins interviews

After a macrotask finishes (a turn of the event loop):

  1. Run all microtasks until the microtask queue is empty
  2. Then pick the next macrotask

Microtasks have higher priority.


Classic output question (and the answer)

console.log('A');
 
setTimeout(() => console.log('B'), 0);
 
Promise.resolve()
  .then(() => console.log('C'))
  .then(() => console.log('D'));
 
console.log('E');

Output

A E C D B

Why

  • A and E run synchronously on the call stack.
  • setTimeout schedules B as a macrotask.
  • Promise .then schedules C as a microtask.
  • After sync finishes, event loop drains microtasks: C then D.
  • Then it runs next macrotask: B.

Another common trick: promises inside timeouts

setTimeout(() => {
  console.log('timeout');
  Promise.resolve().then(() => console.log('microtask inside timeout'));
}, 0);
 
Promise.resolve().then(() => console.log('microtask outside'));

Output

  1. microtask outside
  2. timeout
  3. microtask inside timeout

Because microtasks outside run before the first macrotask. Then inside the timeout, it finishes the macrotask and drains microtasks again.


What about async/await?

Think of await like: “pause here, resume later as a microtask”.

async function run() {
  console.log('1');
  await Promise.resolve();
  console.log('2');
}
 
run();
console.log('3');

Output: 1 3 2


Interview checklist: what to say (30 seconds)

If they ask: “Explain the event loop”

Say:

  • JS runs sync code on the call stack.
  • Async browser/Node work happens in host APIs.
  • Callbacks get queued as macrotasks (timers/I/O) or microtasks (Promise then, queueMicrotask).
  • After each macrotask, the event loop drains all microtasks first, then continues.

Common pitfalls (these get people rejected)

  1. Assuming setTimeout(fn, 0) runs immediately
  • It runs later, after current stack + microtasks.
  1. Not knowing microtasks priority
  • Promise .then runs before timers.
  1. Confusing “single-threaded” with “no concurrency”
  • JS is single-threaded for your code, but the host can do work concurrently.

Quick practice (try to predict)

console.log('start');
setTimeout(() => console.log('timeout'), 0);
queueMicrotask(() => console.log('microtask'));
console.log('end');
Promise.resolve().then(() => {
  console.log('p1');
  setTimeout(() => console.log('t1'), 0);
});
setTimeout(() => console.log('t0'), 0);

(Answer keys: keep for later or in a separate “solutions” post.)


Mini Q&A

Q1: What’s the rule that decides Promise vs setTimeout order?

  • After a macrotask, the event loop drains all microtasks before the next macrotask.

Q2: Is await a microtask?

  • Conceptually, resuming after await happens like a microtask continuation.

Q3: Why do interview “output” questions feel tricky?

  • Because you must model stack → microtasks → macrotasks precisely.

Summary checklist

  • I can explain call stack + host APIs + queues.
  • I know microtasks run before macrotasks.
  • I can solve at least 5 output-order questions.

Summary

  • Microtasks (Promises) run before macrotasks (timers).
  • After each macrotask, the event loop drains the microtask queue completely.
  • If you can reason about 4–5 output questions confidently, you’re set.