Promises, async/await, and the order-of-output trap
If you have written async and await in a Swiggy backend handler and still cannot confidently predict the order of console.log lines, this lesson is for you. The syntax is friendly. The execution model is not.
Most senior interviews open with a "predict the output" promise puzzle. The candidates who pass do not memorise answers. They run the event loop in their head.
Promise states, in one diagram
A Promise has three states. Two of them are terminal. Once fulfilled or rejected, a Promise cannot change again.
.then(onFulfilled, onRejected)attaches handlers..catch(fn)is shorthand for.then(undefined, fn)..finally(fn)runs in both cases, no value passed.- Every
.thenreturns a new Promise. That is how chaining works.
fetchUPIBalance("9876543210")
.then(b => b.available)
.then(amount => `Balance: rupees ${amount}`)
.catch(err => `UPI down: ${err.message}`)
.finally(() => console.log("done"));Mental model: each .then is a station on a train line. The value gets handed forward. A thrown error skips ahead to the next .catch.
async/await is sugar, not magic
An async function always returns a Promise. Always. Even if you write return 5, the caller receives Promise.resolve(5). A throw inside an async function does not bubble synchronously. It becomes a rejected Promise.
async function getOrder(id) {
if (!id) throw new Error("missing id");
return { id, item: "Paneer Tikka" };
}
getOrder().catch(e => console.log(e.message));Step 1: getOrder() is invoked, returns a Promise immediately.
Step 2: Inside, throw happens, but it does not crash the script.
Step 3: That throw becomes a rejected Promise.
Step 4: .catch attached on the outside receives "missing id".
Interview trap: candidates wrap
getOrder()in atry/catchexpecting a sync throw. It does not fire. You must eitherawaitit inside an async function, or chain.catchon the returned Promise.
The Before/Start/After/Data/End trap
This is the puzzle that decides senior rounds. Read carefully.
console.log("Before");
async function load() {
console.log("Start");
const data = await Promise.resolve("IRCTC ticket");
console.log("Data:", data);
}
load();
console.log("After");Predict output: Before, Start, After, Data: IRCTC ticket.
Step 1: "Before" logs synchronously.
Step 2: load() is called. Execution enters the function. "Start" logs.
Step 3: await suspends load. The rest of the function (console.log("Data: ...")) is scheduled as a microtask.
Step 4: Control returns to the caller. "After" logs.
Step 5: The synchronous script ends. The event loop drains microtasks. "Data: IRCTC ticket" logs.
Senior rule: after await, the rest of the function is scheduled as a microtask. Everything outside the function continues first.
Combinators: all, allSettled, race, any
When you need to fan out, pick the right combinator. They differ in what they wait for and how they handle failure.
Promise.all([a, b, c])resolves with an array of all values. Fail-fast: if any one rejects, the whole thing rejects.Promise.allSettled([...])always resolves. You get{status: "fulfilled", value}or{status: "rejected", reason}for each.Promise.race([...])settles as soon as any one settles, fulfilled or rejected.Promise.any([...])resolves with the first fulfilled one. Rejects only if all reject.
const [paytm, phonepe, gpay] = await Promise.allSettled([
checkPaytm(),
checkPhonePe(),
checkGPay(),
]);
const upWallets = [paytm, phonepe, gpay].filter(r => r.status === "fulfilled");Use all when every call must succeed (loading a dashboard that needs all widgets). Use allSettled when partial data is fine (status pings). Use race for timeouts. Use any for "first one that works wins" (CDN fallbacks).
Error handling, the two patterns
You have exactly two clean ways to catch a rejected Promise.
// Pattern A: try/catch around await
async function placeOrder() {
try {
const order = await createOrder();
return order;
} catch (e) {
log("order failed", e);
throw e;
}
}
// Pattern B: .catch on the returned Promise
placeOrder().catch(e => notifyUser(e));Mixing them is fine. Forgetting both is how you get an UnhandledPromiseRejection in production at 2 AM.
Promise vocabulary
- Microtask
- A task that runs after the current synchronous code finishes but before the next macrotask. Promise callbacks live here.
- Settled
- A Promise that is either fulfilled or rejected. No longer pending.
- Fail-fast
- A behaviour where the first failure aborts the whole operation. Promise.all does this.
- Microtask queue
- The FIFO queue of pending Promise continuations the event loop drains between synchronous chunks.
Closing
If you can predict the Before/Start/After/Data/End trap without running it, you understand await. If you can also pick between all, allSettled, race, and any in a code review without thinking, you are interview-ready.
One last thing. await only pauses the function it sits in. The rest of your program keeps moving. That single sentence solves most "why is this running out of order" bugs.
Quick quiz, prove you got it
- 1.Predict: `async function go(){ console.log("Start"); await Promise.resolve("Data"); console.log("End"); } console.log("Before"); go(); console.log("After");`
- 2.What is the difference between Promise.all and Promise.allSettled?
- 3.What does an async function return if you `return 42` from it?
Interview insights
When asked "how does async/await work?"
- Syntax sugar over Promises, nothing more
- await pauses ONLY the async function it is inside; outer code keeps running
- The rest of the function is scheduled as a microtask
- Errors become rejected Promises (not synchronous throws)
Tricky questions they will ask
Q.What happens to a thrown error inside an async function?›
A.It is converted into a rejected Promise. The throw does NOT escape synchronously. You catch it with .catch() on the returned promise or with try/catch around the await call.
Q.Does Promise.all start all promises in parallel?›
A.It does not START them. The promises must already be started before you pass them in. Promise.all just waits. Forgetting this is why people think their "parallel" code is actually sequential.
Free tools you can use while you learn
Common questions
Q.Does an async function block the rest of the script?›
Q.When should I use Promise.all vs Promise.allSettled?›
Watching quietly. Tap me if you want a tip.
Try this (0 of 2 done)
- 1
Confirm the order is: Before, Start, After, Data, End.
hint
Everything after await is a microtask. After runs first because it is synchronous.
show answer
async function getData() { console.log('Start'); const result = await Promise.resolve('Data'); console.log(result); console.log('End'); } console.log('Before'); getData(); console.log('After'); - 2
Use Promise.all to wait for 3 parallel resolved promises, log the result array.
show answer
Promise.all([Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)]) .then(r => console.log(r));