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.
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
- Call Stack
- Where synchronous JS runs.
- One function at a time.
- Web APIs / Host APIs (browser or Node)
- Timers (
setTimeout), network, DOM events, etc. - These are not the JS engine itself.
- Task Queue (Macrotask Queue)
setTimeout,setInterval, I/O, message channels, etc.
- Microtask Queue
- Promise callbacks:
.then/.catch/.finally queueMicrotask- (In browsers)
MutationObserver
- Event Loop
- The coordinator.
- It picks what runs next.
The rule that wins interviews
After a macrotask finishes (a turn of the event loop):
- Run all microtasks until the microtask queue is empty
- 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
AandErun synchronously on the call stack.setTimeoutschedules B as a macrotask.- Promise
.thenschedules C as a microtask. - After sync finishes, event loop drains microtasks:
CthenD. - 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
microtask outsidetimeoutmicrotask 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)
- Assuming
setTimeout(fn, 0)runs immediately
- It runs later, after current stack + microtasks.
- Not knowing microtasks priority
- Promise
.thenruns before timers.
- 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
timeoutStep-by-step execution trace:
- Synchronous call stack:
console.log('start')executes immediately and logs'start'. - 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. - Microtask scheduled:
queueMicrotask(...)places its callback directly into the microtask queue. - Synchronous call stack:
console.log('end')executes immediately and logs'end'. - Draining microtasks: The synchronous script finishes, leaving the call stack empty. Before touching the macrotask queue, the event loop drains all pending microtasks. The
queueMicrotaskcallback runs and logs'microtask'. - Next macrotask: The microtask queue is now empty. The event loop takes the oldest macrotask from the task queue (
setTimeoutcallback) and runs it, logging'timeout'.
Exercise 2 Answer
Output:
p1
t0
t1Step-by-step execution trace:
- Microtask scheduled:
Promise.resolve().then(...)resolves immediately and enqueues its.thencallback into the microtask queue. - Macrotask scheduled:
setTimeout(() => console.log('t0'), 0)registers a timer. The callback for't0'is added as the first entry in the macrotask queue. - Synchronous script complete: The initial synchronous script run finishes.
- 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'.
- Next macrotask (
t0): With the microtask queue exhausted, the event loop picks the oldest macrotask, which is't0'. It executes and logs't0'. - Between macrotasks: The microtask queue is checked again (empty).
- 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
awaithappens 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.
Looking for structured, offline interview prep?
Free online lessons • Portable Amazon Kindle / Paperback
JavaScript Interview Guide
By Rajesh Dhiman
Deep dive into the Event Loop, microtask draining, closures, prototypal inheritance, and asynchronous execution patterns.
Frontend Interview Guide (2025 Edition)
By Rajesh Dhiman
Comprehensive preparation covering React internals, Web Performance, CSS architecture, and system design questions.
Note: All lessons on Frontend Interview remain completely free to read online. As an Amazon Associate, we may earn from qualifying purchases.
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.