Output Quiz #14 — Mixed Async Topics (Hard)

10 expert-level output questions combining concurrent Promise chains, resolve(promise) timing, async error layers, and multi-queue interleaving — the questions that define senior JS interviews.

10 min read
JavaScript
Interview
Output

TABLE OF CONTENTS
Output Quiz #14 — Mixed Async Topics (Hard)

Ten expert-tier output questions combining concurrent chains, resolve(promise) timing, async error layers, and multi-queue interleaving — the questions that define the senior JS interview. If you can get all ten right without peeking, your async mental model is complete.


Q1 — Breadth-first with concrete values

Loading editor...

Show answer

Output:

1
3
5
2
4

Why: Microtask queue initially: [cb1, cb3, cb5]. cb1 runs → 1 → schedules cb2. Queue: [cb3, cb5, cb2]. cb33 → schedules cb4. Queue: [cb5, cb2, cb4]. cb55. Then cb22. Then cb44. Breadth-first: all depth-1 callbacks (cb1, cb3, cb5) run before any depth-2 callback (cb2, cb4).


Q2 — resolve(promise) vs resolve(plain) head start

Loading editor...

Show answer

Output:

plain: plain
inner: inner
extra: extra
outer: inner

Why: resolve(inner) requires adoption — one extra microtask tick. Timeline:

  1. Sync ends. Microtask queue: [adopt, plain-then, inner-then, extra-then]
  2. Adoption runs → outer resolves → queues outer-then. Queue: [plain-then, inner-then, extra-then, outer-then]
  3. plain: plain, inner: inner, extra: extra, outer: inner.

outer's .then() is last because of the adoption overhead. All plain-value chains got a one-tick head start.


Q3 — return vs return await in a nested async chain

Loading editor...

Show answer

Output:

without failed: inner error
caught: inner error
with: recovered

Why: return inner() exits the try block immediately — inner() returns a rejected Promise (because inner is async — throw inside async creates a rejected Promise). The catch block never runs. withoutAwait() returns that rejected Promise, so the caller's .catch() fires.

return await inner() stays inside the try. await unwraps the rejection → inner error is thrown → catch intercepts, returns "recovered". The caller's .then() receives "with: recovered".


Q4 — Promise.race vs Promise.any on first rejection

Loading editor...

Show answer

Output:

sync
race: fast reject
any: slow resolve

Why: "sync" logs first. At ~50ms: fastReject settles. race immediately rejects (first settlement wins). At ~100ms: slowResolve fulfills. any skips rejections so fastReject is ignored — any fulfills with "slow resolve". This is the key difference: race = first to settle (resolve or reject); any = first to fulfill (skips rejections).


Q5 — Promise chain schedules setTimeout, setTimeout schedules Promise

Loading editor...

Show answer

Output:

sync
P1
P2
T2
P-in-T2
T1
P-in-T1

Why: Sync: "sync". Microtask drain: P1 → queues macrotask T1 → queues P2. Then P2 runs. Macrotask queue now: [T2, T1] (T2 was scheduled during sync, T1 during microtask). T2"T2" → queues microtask P-in-T2. Microtask drain: "P-in-T2". T1"T1" → queues microtask P-in-T1. Microtask drain: "P-in-T1". The key: macrotask scheduled inside a microtask goes to the end of the task queue, behind already-scheduled macrotasks.


Q6 — Async error propagation through three layers

Loading editor...

Show answer

Output:

A
B
C
sync
A caught: from C

Why: All pre-await code runs synchronously: "A", "B", "C". Then "sync". C() returns a rejected Promise. B()'s await re-throws it, making B() return a rejected Promise. A()'s await re-throws it, landing in A's catch. "after B" is unreachable. The error propagates up through each await like a synchronous throw through a call stack.


Q7 — Promise.allSettled output shape

Loading editor...

Show answer

Output:

3
fulfilled → ok
rejected → fail
fulfilled → delayed

Why: Promise.allSettled waits for all Promises (including the 50ms delay) and returns an array in input order. Fulfilled: {status:"fulfilled", value:...}. Rejected: {status:"rejected", reason:...}. All three are represented — nothing is lost. The 50ms delay means the whole thing takes ~50ms, but the structure doesn't change. allSettled never rejects.


Q8 — await inside .map() finishes before mapped promises

Loading editor...

Show answer

Output:

p is array of: Promises
sync
results: [10, 20, 30]

Why: .map(async fn) returns an array of Promises — "P" logs "Promises". main() suspends at await Promise.all(p) — the continuations (printing results) are microtasks. "sync" logs. After ~300ms (the longest timer), all Promises resolve: [10, 20, 30]. Note: even though n=1 resolves first at 100ms, the array order is preserved by Promise.all.


Q9 — for await...of sequential timing

Loading editor...

Show answer

Output (approx):

a 100 ms
b 100 ms
c 150 ms

Why: All three Promises are started at the same time (inside the promises array). for await...of awaits them in order: first promises[0] (~100ms) → "a" at ~100ms. Then promises[1] — already resolved at 50ms, so it yields immediately: "b" at ~100ms. Then promises[2] (~150ms) → "c" at ~150ms. Total time is the longest Promise (~150ms), not the sum of delays. Each iteration blocks on the current element's resolution.


Q10 — Everything at once

Loading editor...

Show answer

Output:

f1-start
f2-start
DONE
QM
f1-mid
f2 f2-error
all-ok all-ok2
f1-end
T
P-in-T

Why: Full tick-by-tick:

Sync: "f1-start", "f2-start", "DONE". f2() throws: its returned Promise is immediately rejected (.catch() will fire as microtask). f1() suspends at first await null. Promise.all is all-resolved → .then() queued as microtask. queueMicrotask queues QM. Macrotask: T.

Microtask queue after sync: [f1-mid, f2-catch, all-then, QM]

  • f1-mid"f1-mid" → second await → schedules f1-end at end
  • f2-catch"f2 f2-error" (the catch fires — f2 returned a rejected Promise)
  • all-then"all-ok all-ok2"
  • QM"QM"
  • Now: [f1-end]"f1-end"

Macrotask: "T" → queues microtask P-in-T → microtask drain: "P-in-T".


Key Rules

PatternRule
Breadth-first interleavingMultiple .then() chains alternate by depth: depth-1 all run before any depth-2
resolve(promise) extra tickAdoption = 1 extra microtask — chains using resolve(plain) get a head start
return fn() vs return await fn()return exits try immediately; return await keeps the error inside
Promise.race vs Promise.anyrace = first settled; any = first fulfilled (skips rejections)
setTimeout from .then()Macrotask is queued at the end — runs after previously scheduled macrotasks
Error through await layersPropagates like sync call stack — each await re-throws
Promise.allSettledNever rejects; {status,value} or {status,reason} per entry
[].map(async fn)Returns array of Promises — wrap with Promise.all to await
for await...ofAwaits in order; all Promises start immediately; total time = longest

Go Deeper


Let's Connect

© 2026 Naveen Karthik // Built with React & MUI