async/await hides a lot of Promise mechanics — and interviewers know exactly where the gaps are. The return vs return await trap in try/catch catches nearly every developer once. Ten questions on the patterns that separate knowing the syntax from understanding the model.
Q1 — await on a non-Promise suspends too
Loading editor...
Show answer
Output:
1
2
42
3
Why: await 42 is equivalent to await Promise.resolve(42). Even though 42 is a plain number, await always suspends the function and schedules the rest as a microtask — regardless of whether the value is a Promise. So 2 logs synchronously before 42 and 3.
Q2 — Code before the first await is synchronous
Loading editor...
Show answer
Output:
before
setup start
after
setup end
Why: The code inside an async function runs synchronously until the first await. "setup start" logs as part of the same synchronous turn as "before" and "after". The await null is the suspension point — everything after it ("setup end") is scheduled as a microtask and runs later.
Q3 — Error thrown before first await creates a rejected Promise
Loading editor...
Show answer
Output:
true
sync after boom()
caught: immediate
Why: An async function always returns a Promise. If it throws before reaching any await, the returned Promise is immediately rejected. p is a rejected Promise — p instanceof Promise is true. The .catch() callback fires as a microtask, so "sync after boom()" logs first.
Q4 — return Promise.reject() in try is NOT caught
Loading editor...
Show answer
Output:
caught outside: error
Why: return Promise.reject("error") exits the try block immediately — the catch block never runs. The rejected Promise is returned from the function (and thus from the async function's own returned Promise). The try/catch only catches synchronous throws and await-ed rejections. A bare return of a rejected Promise is neither.
Q5 — return await Promise.reject() IS caught
Loading editor...
Show answer
Output:
caught in catch: error
result: recovered
Why: return await Promise.reject("error") first awaits the rejected Promise — which throws at the await expression — which the surrounding try/catch can intercept. The catch block recovers and returns "recovered", so the outer .then() receives it. This is the critical difference: return exits immediately; return await stays inside the try and gives the catch block a chance.
Q6 — Sequential await vs Promise.all timing
Loading editor...
Show answer
Output:
parallel: a b
sequential: a b
Why: parallel() runs both delay(100) calls concurrently — they start at the same time and both resolve after ~100ms total. sequential() waits for each in turn — 100ms for a, then another 100ms for b, totalling ~200ms. parallel() finishes first and logs first. Both log the same values but the timing differs significantly in real code with actual I/O.
Q7 — Async IIFE suspends independently
Loading editor...
Show answer
Output:
before
iife start
after
iife end
Why: The async IIFE is invoked immediately (like any IIFE) and returns a Promise. Its body runs synchronously until await null, which suspends it. Execution returns to the outer scope: "after" logs. Then the resumed IIFE continuation fires as a microtask: "iife end" logs.
Q8 — await inside .map() doesn't pause the outer function
Loading editor...
Show answer
Output:
results type: true
first element: true
resolved: [2, 4, 6]
Why: async callbacks passed to .map() each return a Promise — so results is an array of Promises, not an array of values. The await inside each callback suspends only that callback, not main(). This is one of the most common async bugs: developers expect .map(async fn) to produce resolved values, but it produces an array of Promises. The fix is await Promise.all(results).
Q9 — Nested async functions — how many ticks to resume outer?
Loading editor...
Show answer
Output:
outer before await
sync
outer after await: inner value
Why: inner() is an async function that returns "inner value" — it resolves immediately. await inner() suspends outer() and schedules the continuation as a microtask (one tick). "sync" logs synchronously. Then the microtask fires and outer resumes with "inner value". One await = one microtask suspension, regardless of whether the awaited value was already resolved.
Q10 — for await...of iterates sequentially
Loading editor...
Show answer
Output:
a
b
c
Why: for await...of on an array of Promises awaits each one in order. Even though "b" resolves fastest (50ms), the loop awaits promises[0] first (100ms), then promises[1] (already resolved by then), then promises[2] (150ms). All three Promises are started at the same time, but the loop yields values in input order as each resolves. Total time is ~150ms (the longest), not the sum.
Key Rules
| Pattern | Behaviour |
|---|---|
await nonPromise | Wraps in Promise.resolve(); still suspends one microtask tick |
Code before first await | Runs synchronously as part of the caller's turn |
throw before first await | Returned Promise is immediately rejected |
return Promise.reject() in try | NOT caught — exits try before rejection is observed |
return await Promise.reject() in try | IS caught — await throws inside the try block |
Sequential await a; await b | Waits for each in turn — total time = sum of each wait |
await Promise.all([a, b]) | Concurrent — total time = max of waits |
[].map(async fn) | Returns array of Promises — wrap with Promise.all to await all |
for await...of promises[] | Awaits in input order; all Promises start immediately |
Go Deeper
- Output Quiz #8 — Promise Combinators — the previous quiz
- Output Quiz #10 — setTimeout + Promises: Real-World Interleaving — the next quiz
- Output Quiz #6 — Promises & async/await Ordering — basics that this quiz builds on
- Output Quiz #3 — The Event Loop & Task Ordering — the microtask model behind await suspension
