Skip to content

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.

Rajesh DhimanFebruary 08, 2026~10 min
On this page

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);

Answers and step-by-step traces

Exercise 1 Answer

Output:

start
end
microtask
timeout

Step-by-step execution trace:

  1. Synchronous call stack: console.log('start') executes immediately and logs 'start'.
  2. Macrotask scheduled: setTimeout(..., 0) registers a timer with the host environment. When the 0ms delay elapses, the host places the callback into the macrotask (task) queue.
  3. Microtask scheduled: queueMicrotask(...) places its callback directly into the microtask queue.
  4. Synchronous call stack: console.log('end') executes immediately and logs 'end'.
  5. Draining microtasks: The synchronous script finishes, leaving the call stack empty. Before touching the macrotask queue, the event loop drains all pending microtasks. The queueMicrotask callback runs and logs 'microtask'.
  6. Next macrotask: The microtask queue is now empty. The event loop takes the oldest macrotask from the task queue (setTimeout callback) and runs it, logging 'timeout'.

Exercise 2 Answer

Output:

p1
t0
t1

Step-by-step execution trace:

  1. Microtask scheduled: Promise.resolve().then(...) resolves immediately and enqueues its .then callback into the microtask queue.
  2. Macrotask scheduled: setTimeout(() => console.log('t0'), 0) registers a timer. The callback for 't0' is added as the first entry in the macrotask queue.
  3. Synchronous script complete: The initial synchronous script run finishes.
  4. Draining microtasks: The event loop checks the microtask queue and finds the promise handler. It runs:
    • console.log('p1') executes synchronously and logs 'p1'.
    • setTimeout(() => console.log('t1'), 0) is called inside the microtask. The host registers this timer and appends 't1' to the macrotask queue behind 't0'.
  5. Next macrotask (t0): With the microtask queue exhausted, the event loop picks the oldest macrotask, which is 't0'. It executes and logs 't0'.
  6. Between macrotasks: The microtask queue is checked again (empty).
  7. Next macrotask (t1): The event loop moves to the next macrotask in the queue, 't1'. It executes and logs 't1'.

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.

Written by Rajesh Dhiman at Eunix Tech for the Frontend Interview preparation track.

Companion Book

Looking for structured, offline interview prep?

Free online lessons • Portable Amazon Kindle / Paperback

JavaScript Interview Guide cover
Recommended for Core JS Rounds

JavaScript Interview Guide

By Rajesh Dhiman

Deep dive into the Event Loop, microtask draining, closures, prototypal inheritance, and asynchronous execution patterns.

View on Amazon ↗Kindle & Paperback
Frontend Interview Guide (2025 Edition) cover
Recommended for React & Frontend Rounds

Frontend Interview Guide (2025 Edition)

By Rajesh Dhiman

Comprehensive preparation covering React internals, Web Performance, CSS architecture, and system design questions.

View on Amazon ↗Kindle & Paperback

Note: All lessons on Frontend Interview remain completely free to read online. As an Amazon Associate, we may earn from qualifying purchases.

RD

Written by Rajesh Dhiman

Authored by Rajesh Dhiman and engineered at Eunix Tech. Focused on deep-dive frontend engineering, JavaScript runtime internals, React architecture, and web performance.