The mental model before you start:
- Call stack โ synchronous code runs here, one frame at a time.
- Microtask queue โ Promise
.then,.catch,.finally,async/awaitcontinuations,queueMicrotask. Drains completely after each task. - Macrotask queue (task queue) โ
setTimeout,setInterval,setImmediate(Node), I/O callbacks. One task per loop tick.
Order: current synchronous task โ drain all microtasks โ pick one macrotask โ drain all microtasks โ pick next macrotask โ โฆ
Q1 โ the classic A/D/C/B
console.log. Async logs (setTimeout/Promise) appear in real execution order.Answer & why
A
D
C
BAโ synchronous, runs immediately.BโsetTimeoutschedules a macrotask.CโPromise.resolve().thenschedules a microtask.Dโ synchronous, runs immediately.- After the script finishes: drain microtasks โ
Cfires. - Pick the next macrotask โ
Bfires.
Q2 โ Promise constructor is synchronous
console.log. Async logs (setTimeout/Promise) appear in real execution order.Answer & why
1
2
4
5
3The executor function (the callback you pass to new Promise) runs synchronously. Only .then is asynchronous (microtask). So 2 and 4 log before the script reaches 5. After the script, the microtask runs and logs 3.
Q3 โ chained promises ordering
console.log. Async logs (setTimeout/Promise) appear in real execution order.Answer & why
E
A
D
B
CBoth chains queue their first .then as microtasks at the same time. After E (sync), the microtask queue is: [A-handler, D-handler].
- A-handler runs โ logs
A, returns'B'โ queues B-handler. - D-handler runs โ logs
D. - B-handler runs โ logs
B, queues C-handler. - C-handler runs โ logs
C.
Microtasks interleave between chains โ itโs a single FIFO queue.
Q4 โ async/await unwrapping
console.log. Async logs (setTimeout/Promise) appear in real execution order.Answer & why
3
1
4
2foo() starts synchronously: logs 1, then hits await. await suspends foo (schedules the continuation as a microtask) and returns control to the caller. The caller logs 4. After the synchronous script ends, the microtask queue runs and resumes foo โ logs 2.
Q5 โ await inside a loop (series not parallel)
console.log. Async logs (setTimeout/Promise) appear in real execution order.Answer & why
start
sync after run()
A
B
doneEach await suspends the async function. 'sync after run()' logs immediately after run() is called (before the first delay resolves). Then each delay resolves sequentially (because each await waits for the previous one).
Q6 โ Promise.all vs sequential await
console.log. Async logs (setTimeout/Promise) appear in real execution order.Answer & why
seq: A B (after ~200ms)
par: C D (after ~300ms total, ~100ms after seq)Sequential await means the second delay doesnโt even start until the first resolves. Total: 200ms. Promise.all starts both simultaneously โ both fire at the same time. Total extra: 100ms. The ordering of these two logs is always seq then par.
Q7 โ microtask flood
console.log. Async logs (setTimeout/Promise) appear in real execution order.Answer & why
sync
p1
p2
p3
timeoutThe microtask queue drains completely before any macrotask runs. Even though the chain creates new microtasks on the fly (each .then adds the next to the queue), they all resolve before the setTimeout callback fires. timeout always comes last.
Q8 โ async error handling order
console.log. Async logs (setTimeout/Promise) appear in real execution order.Answer & why
1
5
3: oops
4run() executes 1 synchronously, then hits await fail(). fail() throws synchronously but since itโs an async function, the error becomes a rejected Promise. await catches the rejection and resumes at the catch block as a microtask. Meanwhile 5 (sync) runs. Then the microtask fires: 3: oops, then 4.
2 never prints โ await threw.
Q9 โ nested async functions
console.log. Async logs (setTimeout/Promise) appear in real execution order.Answer & why
outer start
inner start
sync
inner end
outer end: resultouter logs outer start, calls inner. inner logs inner start, hits await โ suspends. Control returns to outer, which also awaits inner. Control returns to the synchronous code โ sync.
Microtask 1: inner resumes โ inner end โ returns 'result'.
Microtask 2: outer resumes with r = 'result' โ outer end: result.
Q10 โ queueMicrotask vs Promise.resolve
console.log. Async logs (setTimeout/Promise) appear in real execution order.Answer & why
1
6
3
4
5
2queueMicrotask adds directly to the microtask queue โ same as Promise.resolve().then(โฆ). All microtasks drain before the macrotask (setTimeout). The microtask queue order is FIFO: 3 was queued first, then 4 (from Promise.then), then 5.
Q11 โ Promise.resolve with a thenable
console.log. Async logs (setTimeout/Promise) appear in real execution order.Answer & why
sync
valuePromise.resolve(promise) returns the same promise when passed a native Promise โ it doesnโt wrap it. The .then callback fires as a microtask after sync code. The value is 'value'. (Note: if you pass a thenable thatโs not a native Promise, thereโs an extra microtask tick โ but for native Promises, itโs resolved immediately.)
Q12 โ setTimeout(fn, 0) race with multiple promises
console.log. Async logs (setTimeout/Promise) appear in real execution order.Answer & why
start
end
p1
p2
timeout 1
timeout 2Both setTimeout callbacks are macrotasks queued in order. Both .then callbacks are microtasks. After end (sync), the microtask queue drains fully (p1 โ p2). Then macrotasks run one by one: timeout 1 โ timeout 2.
.then and await continuations are microtasks. setTimeout/setInterval are macrotasks. The microtask queue always beats the macrotask queue.