Output Questions: Event Loop & Async

Predict execution order โ€” setTimeout vs Promise vs queueMicrotask, async/await unwrapping, and nested async patterns.

must hard โฑ 30 min event-looppromisesasync-awaitsetTimeoutmicrotasksmacrotasks
Mastery:
Why interviewers ask this
Async ordering is the #1 hardest JS output question category. Getting A/D/C/B right signals you genuinely understand the call stack, microtask queue, and macrotask queue.

The mental model before you start:

  • Call stack โ€” synchronous code runs here, one frame at a time.
  • Microtask queue โ€” Promise .then, .catch, .finally, async/await continuations, 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

Run it yourself
Edit and run. Output is captured from console.log. Async logs (setTimeout/Promise) appear in real execution order.
Console
 
Answer & why
A
D
C
B
  1. A โ€” synchronous, runs immediately.
  2. B โ€” setTimeout schedules a macrotask.
  3. C โ€” Promise.resolve().then schedules a microtask.
  4. D โ€” synchronous, runs immediately.
  5. After the script finishes: drain microtasks โ†’ C fires.
  6. Pick the next macrotask โ†’ B fires.

Q2 โ€” Promise constructor is synchronous

Run it yourself
Edit and run. Output is captured from console.log. Async logs (setTimeout/Promise) appear in real execution order.
Console
 
Answer & why
1
2
4
5
3

The 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

Run it yourself
Edit and run. Output is captured from console.log. Async logs (setTimeout/Promise) appear in real execution order.
Console
 
Answer & why
E
A
D
B
C

Both 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

Run it yourself
Edit and run. Output is captured from console.log. Async logs (setTimeout/Promise) appear in real execution order.
Console
 
Answer & why
3
1
4
2

foo() 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)

Run it yourself
Edit and run. Output is captured from console.log. Async logs (setTimeout/Promise) appear in real execution order.
Console
 
Answer & why
start
sync after run()
A
B
done

Each 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

Run it yourself
Edit and run. Output is captured from console.log. Async logs (setTimeout/Promise) appear in real execution order.
Console
 
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

Run it yourself
Edit and run. Output is captured from console.log. Async logs (setTimeout/Promise) appear in real execution order.
Console
 
Answer & why
sync
p1
p2
p3
timeout

The 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

Run it yourself
Edit and run. Output is captured from console.log. Async logs (setTimeout/Promise) appear in real execution order.
Console
 
Answer & why
1
5
3: oops
4

run() 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

Run it yourself
Edit and run. Output is captured from console.log. Async logs (setTimeout/Promise) appear in real execution order.
Console
 
Answer & why
outer start
inner start
sync
inner end
outer end: result

outer 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

Run it yourself
Edit and run. Output is captured from console.log. Async logs (setTimeout/Promise) appear in real execution order.
Console
 
Answer & why
1
6
3
4
5
2

queueMicrotask 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

Run it yourself
Edit and run. Output is captured from console.log. Async logs (setTimeout/Promise) appear in real execution order.
Console
 
Answer & why
sync
value

Promise.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

Run it yourself
Edit and run. Output is captured from console.log. Async logs (setTimeout/Promise) appear in real execution order.
Console
 
Answer & why
start
end
p1
p2
timeout 1
timeout 2

Both 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.

The rule that never changes
Sync โ†’ microtasks (drain completely, including newly added ones) โ†’ one macrotask โ†’ microtasks again โ†’ next macrotask. Promise .then and await continuations are microtasks. setTimeout/setInterval are macrotasks. The microtask queue always beats the macrotask queue.

Likely follow-up questions
  • What is the difference between microtask and macrotask queues?
  • Why do Promise callbacks run before setTimeout(fn, 0)?
  • What does await actually do to execution?
  • What is queueMicrotask?
  • When does the microtask queue drain?

References