Output Quiz #9 — async/await Patterns & the return vs return await Trap

10 output questions exposing the return-vs-return-await pitfall in try/catch, sequential vs parallel timing, await on non-Promises, and errors thrown before the first await.

10 min read
JavaScript
Interview
Output

TABLE OF CONTENTS
Output Quiz #9 — async/await Patterns & the return vs return await Trap

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

PatternBehaviour
await nonPromiseWraps in Promise.resolve(); still suspends one microtask tick
Code before first awaitRuns synchronously as part of the caller's turn
throw before first awaitReturned Promise is immediately rejected
return Promise.reject() in tryNOT caught — exits try before rejection is observed
return await Promise.reject() in tryIS caught — await throws inside the try block
Sequential await a; await bWaits 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


Let's Connect

© 2026 Naveen Karthik // Built with React & MUI