Q1. Why does the import say "done" when nothing is done?
Level: Junior · Format: Debug this snippet · Time: ~8 min
What it probes: Whether they understand how async callbacks behave inside array methods, and what happens to a rejected promise that nothing awaits.
Rung 0: ask
"The client gets { imported: 200 } back right away. But some orders never show up, and the server sometimes crashes a few seconds later. What's wrong?"
app.post('/orders/import', async (req, res) => {
const { orders } = req.body;
orders.forEach(async (order) => {
await db.orders.insert(order);
await inventory.reserve(order.sku, order.qty);
});
res.json({ imported: orders.length });
});
Answer key:
forEach ignores the promises returned by the async callback. The handler responds immediately, before any insert finishes.
- If an insert or reserve rejects, nothing is awaiting it, so it's an unhandled rejection. Since Node 15, the default behaviour for unhandled rejections is to crash the process. That's the "crash a few seconds later."
- Express 5 forwards rejected handler promises to error middleware, but these rejections happen inside callbacks the handler never awaits, so Express can't see them.
- All 200 orders also run at once, with no concurrency limit and no transaction. A partial failure leaves orders inserted without reserved inventory.
- Fix:
for...of with await (sequential), or Promise.all / Promise.allSettled with a concurrency limit. Wrap each order's insert and reserve in a transaction. Return per-order results. For big imports, enqueue a job and return 202 Accepted with a job id.
Rung 1: perturb
- "The client now sends 20,000 orders." Good: don't do this in the request. Use a background job, a progress endpoint, batching, and a concurrency limit.
- "One bad order shouldn't fail the whole import." Good:
Promise.allSettled or per-item try/catch, return which ones failed and why, and make retries idempotent (for example with an upsert on an external order id).
Rung 2: derive
- "Why not just
await Promise.all(orders.map(...))?" (That fixes the ordering, but it fires everything at once, and the first rejection rejects the whole call while the other inserts keep running.)
- "How would you have caught this before production?" (A test asserting rows exist after the response, the
no-misused-promises / no-floating-promises lint rules, and an unhandledRejection log in staging.)
- "What should happen on
unhandledRejection in production?" (Log it with context and let the process crash and restart. Don't swallow it.)
Rung 3: own it
"Tell me about a bug you shipped or debugged that came down to a promise nobody was waiting for. How did it show up?"
Strong signals
- Spots the
forEach problem quickly, and explains the crash too.
- Raises partial failure and transactions without prompting.
- Mentions the lint rules that catch floating promises.
Scripted/weak signals
- "Make the callback non-async" or "add
.catch inside" with no fix for the early response.
- Jumps to
Promise.all and doesn't see the concurrency or partial-failure issue when pushed.
- Can't explain why the process crashes.
Listen for (non-technical recruiter)
- Good candidates describe it as "the server says it finished before it actually finished."
- Be wary if they fix only the crash and not the wrong "done" message, or the other way round.