Async Execution Ordering
Key Learning Objectives
Trace asynchronous interleaving across synchronous Call Stack execution, Microtask Queue, and Task Queue.
Understand the synchronous execution of Promise constructor executors.
Master async/await desugaring: synchronous execution up to the first await expression, followed by microtask resumption.
Analyze chained Promise .then() callbacks and understand how promise resolution queues subsequent microtasks in turn.
Master event loop queue draining order: all microtasks drain completely before the next macrotask (setTimeout) executes.
The Interview Problem
What is logged to the console when the following code executes, and how do async/await expressions interleave with chained Promise.then callbacks and timer macrotasks?
1const log = [];23log.push('1');45setTimeout(() => {6 log.push('2');7 console.log(log.join(' '));8}, 0);910async function asyncFn() {11 log.push('3');12 await null;13 log.push('4');14}1516asyncFn();1718new Promise((resolve) => {19 log.push('5');20 resolve();21})22 .then(() => {23 log.push('6');24 })25 .then(() => {26 log.push('7');27 });2829log.push('8');
Predict Console Output
Select the option that matches what standard ECMAScript prints to the console:
1 3 5 8 4 6 7 2
1 8 3 5 4 6 7 2
1 3 5 8 2 4 6 7
1 3 5 8 6 7 4 2
V8 Engine Execution Trace
Step 1 of 11 (Line 3)Pushes '1' synchronously.
Deep Technical Breakdown
The Step-by-Step V8 Execution Model
To master async execution order questions in interviews, follow this deterministic 3-tier mental model:
Tier 1: The Synchronous Execution Phase
Code on the Call Stack executes immediately from top to bottom:
log.push('1')runs.setTimeout(..., 0)registers a timer with the browser/Node host environment, placing its callback into the Macrotask Queue.asyncFn()is invoked. Contrary to common belief, calling anasyncfunction is synchronous until the firstawait!log.push('3')executes immediately.- When
await nullis reached, JavaScript wraps the operand in a Promise (Promise.resolve(null)), attaches the remainder ofasyncFnas a fulfillment handler, and returns control to the caller. This enqueues Microtask #1 (resume asyncFn). new Promise(executor)is evaluated. The executor function executes synchronously immediately upon creation.log.push('5')runs, followed byresolve().- The
.then(() => log.push('6'))attaches to the already-resolved promise, enqueuing Microtask #2. log.push('8')runs synchronously.
At the end of Tier 1, the console state is [1, 3, 5, 8].
Tier 2: The Microtask Draining Phase
Whenever the Call Stack empties, the engine must exhaustively drain the Microtask Queue before touching any macrotasks:
- Microtask #1: Resumes
asyncFn(). Pushes'4'. Queue now has[Microtask #2]. - Microtask #2: Executes the first
.then(). Pushes'6'. Its completion resolves the promise returned by the first.then(), which schedules Microtask #3 (the chained.then()). - Microtask #3: Executes the second
.then(). Pushes'7'.
The Microtask Queue is now completely empty.
Tier 3: The Macrotask Phase
The event loop selects the oldest task in the Macrotask Queue:
- The
setTimeoutcallback runs. Pushes'2'. console.log(log.join(' '))logs'1 3 5 8 4 6 7 2'.
Common Traps & Mistakes
Believing `async` functions defer their entire execution to a microtask. Only the code *after* the `await` keyword is deferred.
Thinking `new Promise((resolve) => { ... })` runs asynchronously. The executor runs synchronously on the active call stack frame.
Assuming `setTimeout(fn, 0)` can execute before microtasks. Macrotasks are strictly held until the microtask queue is 100% empty.
Failing to understand that chained `.then()` callbacks cannot be enqueued until the preceding `.then()` callback finishes executing.
FAANG Follow-Up Probes
Probe #1
How does `queueMicrotask` differ in execution priority compared to `Promise.resolve().then()`?
Probe #2
What would happen if a microtask continuously enqueued more microtasks in an infinite loop? (Event loop starvation: UI freezes and timers never execute).
Probe #3
How does the browser rendering step (style recalculation, layout, paint) interleave between microtasks and macrotasks?
