JavaScript Interview Prep
Event Loop Execution Order Questions
Master event loop execution order interview questions. Learn to trace microtask vs macrotask execution timelines step-by-step.
1. Introduction
Event Loop execution order questions test your ability to trace asynchronous code line-by-line. To solve these puzzles, you must track synchronous code execution on the Call Stack, followed by Microtasks (Promises, queueMicrotask), and finally Macrotasks (setTimeout, setInterval).
2. Golden Rules of Execution Order
- 1. Synchronous Code First: Code inside main scripts or inside
new Promise((resolve) => { ... })executors runs synchronously on the Call Stack immediately. - 2. Microtask Queue Drain: When the Call Stack empties, the Event Loop drains the ENTIRE Microtask Queue (Promise
.then/catch/finallycallbacks,queueMicrotask). - 3. Macrotask Queue (One per tick): After all microtasks finish, the Event Loop takes the oldest single Macrotask (
setTimeout,setInterval). - 4. Repeat: After processing the macrotask, the Event Loop checks the Microtask Queue again before taking the next macrotask.
3. Essential Event Loop Puzzles
Puzzle 1: Basic Promise Executor vs SetTimeout
console.log('1');setTimeout(() => { console.log('2'); }, 0);
new Promise((resolve) => { console.log('3'); resolve(); }).then(() => { console.log('4'); });
console.log('5');
View Step-by-Step Execution
Output: "1", "3", "5", "4", "2"
Step-by-Step Breakdown:
1. Synchronous: console.log('1') -> logs 1.
2. setTimeout callback is pushed to Macrotask Queue.
3. new Promise executor runs synchronously: console.log('3') -> logs 3. resolve() queues .then() callback to Microtask Queue.
4. Synchronous: console.log('5') -> logs 5. Call Stack is now empty!
5. Drain Microtasks: .then() callback runs -> logs 4.
6. Process Macrotask: setTimeout callback runs -> logs 2.
Puzzle 2: Async / Await Unwrapping
async function async1() { console.log('async1 start'); await async2(); console.log('async1 end'); }async function async2() { console.log('async2'); }
console.log('script start');
setTimeout(() => { console.log('setTimeout'); }, 0);
async1();
new Promise((resolve) => { console.log('promise1'); resolve(); }).then(() => { console.log('promise2'); });
console.log('script end');
View Step-by-Step Execution
Output:
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
Key Insight: await async2() runs async2() synchronously, then queues code following await into the Microtask Queue!
4. Quick Quiz
Q1: Code inside a new Promise constructor callback executes in which phase?
A) Microtask Queue
B) Synchronously on the Call Stack
Answer: B — The executor function passed to new Promise((resolve, reject) => { ... }) executes synchronously immediately.
5. Production Considerations
- • Avoid Starving the Macrotask Queue: Recursively queuing microtasks (e.g. infinite
queueMicrotaskloops) blocks the Event Loop from processing macrotasks or browser rendering, causing UI freezes.