How does async/await actually work under the hood, and how do you avoid accidentally serialising independent awaits?
Whether you understand async/await is syntax over promises — and can spot the classic 'await in a loop' performance trap.
An async function ALWAYS returns a promise; `return x` resolves it with x, `throw` rejects it. `await p` suspends the function until p settles, then resumes with the resolved value (or throws the rejection reason into the surrounding try/catch). It is pure syntax over promises + the microtask queue — nothing runs on a separate thread; the function yields control back to the event loop at each await and resumes as a microtask when the awaited promise settles. The #1 mistake is treating await as 'do this, then that' when the operations are INDEPENDENT: `const a = await f(); const b = await g();` runs g only after f finishes (sequential, sum of both latencies). If they don't depend on each other, kick both off first and await together: `const [a, b] = await Promise.all([f(), g()])` (parallel, max of the two). The same trap hides inside `for` loops with `await` in the body — each iteration waits for the previous; map to a promise array and Promise.all instead when order-independent. Error handling: wrap awaits in try/catch, or attach .catch to the returned promise. Remember an un-awaited async call is a floating promise — unhandled rejections can crash Node; always await or .catch.
Sequencing async work; converting promise chains to readable linear code; and diagnosing slow request waterfalls that should be parallel.