Interviewers rarely ask for the thing they want to know. They ask for something adjacent, cheap to answer, and easy to score, and then they listen to how you got there.
That is not a trick. It is the only practical way to run the interview. The stated question has an answer that is freely available and takes an evening to memorise. The question behind it does not, because answering that one requires having been on the wrong side of it at some point.
Below are seven snippets. Nothing exotic, nothing from a puzzle site. For each one: what gets asked, what is actually being measured, and the follow-up that arrives if you answer only the surface.
Read the code before you read the commentary. It is more useful that way.
1. The method that loses its object
class Rates {
constructor(map) { this.map = map; }
lookup(code) { return this.map[code]; }
}
const rates = new Rates({ EUR: 1.0, USD: 1.09 });
codes.map(rates.lookup);
// TypeError: Cannot read properties of undefined (reading 'EUR')
Enter fullscreen mode Exit fullscreen mode
Asked: why does this throw?
Actually asked: do you know that this is decided by the call, not by where the function was written?
The answer that scores says the binding is resolved at call time from the receiver, and map calls the function with no receiver, so inside a class body (which is strict) this is undefined rather than the global object. Then it says what to do: .map((c) => rates.lookup(c)), or bind it, or make lookup an arrow-valued field so it closes over the instance instead.
The version that only says “you lost the this context” is correct and stops one layer short of where the question was aimed.
The follow-up: and what would the arrow field cost you? (One function per instance rather than one on the prototype, which is fine at almost any scale and worth being able to say out loud.)
2. The loop that is slow on purpose, or by accident
const users = [];
for (const id of ids) {
users.push(await fetchUser(id));
}
Enter fullscreen mode Exit fullscreen mode
Asked: what is wrong with this?
Actually asked: can you tell the difference between sequential-because-it-has-to-be and sequential-by-accident?
Most people say “use Promise.all” and that is where it ends. The stronger answer notices this only matters when the calls are independent, then volunteers the cases where the loop is right: each call depends on the last one’s result, or the endpoint is rate limited and firing two hundred requests at once gets you a 429 and a slower total, or you are writing rows that have to land in order.
If you do fan it out, ids.map((id) => fetchUser(id)) and not ids.map(fetchUser), because map passes the index as a second argument and plenty of functions quietly accept one.
The follow-up: two hundred ids. Still Promise.all? (No. That is a concurrency limit, and being able to say so is most of the point of the question.)
3. The Promise.all that leaves money on the floor
const [charge, reservation, receipt] = await Promise.all([
chargeCard(order),
reserveStock(order),
sendReceipt(order),
]);
Enter fullscreen mode Exit fullscreen mode
Asked: what happens if reserveStock rejects?
Actually asked: do you know what happens to the other two?
Promise.all rejects on the first rejection, but it does not cancel anything. The card charge is still in flight and will still succeed, and you have just thrown away the only reference to its result. You now have a customer who has paid for stock you never reserved, and no handle on the charge id to refund it.
The answer that lands names the state you are left in, not just the control flow. Then it says what you would do instead: allSettled when you need every outcome, sequential when a later step depends on an earlier one committing, and a compensating action for the leg that already succeeded.
The follow-up: what shows up in your logs when the other two reject afterwards? (Unhandled rejections, because Promise.all already settled and nothing is attached to them any more.)
4. The try block that catches nothing
try {
processQueue();
} catch (err) {
logger.error({ err }, "queue failed");
}
Enter fullscreen mode Exit fullscreen mode
Asked: why is nothing being logged?
Actually asked: do you understand that catch is about a stack frame, not about a block of text?
processQueue is async and is not awaited, so it returns a promise and the try block completes normally. The rejection arrives later, on a frame that no longer has this handler on it. Adding await fixes it. So does .catch().
This is worth being able to say precisely, because it is the same mechanism behind a catch that misses a setTimeout callback, a rejection inside an event listener, and half the “we have logging and we still cannot see the error” incidents.
The follow-up: what does Node do with that rejection now? (Crashes the process by default since v15, which surprises people who remember the warning.)
5. The floats holding money
const total = items.reduce((sum, i) => sum + i.price, 0);
if (total === amountPaid) {
markAsPaid(order);
}
Enter fullscreen mode Exit fullscreen mode
Asked: what is the bug?
Actually asked: do you know why it happens, and do you know where it stops being theoretical?
Everyone can recite 0.1 + 0.2 !== 0.3. The interesting part is that binary floating point cannot represent tenths exactly, the error is tiny per operation, and it accumulates over a reduce. The equality check is where it surfaces, but the corruption happened earlier, which is why a tolerance comparison is a patch and not a fix.
Store minor units as integers, or use a decimal type your database actually understands, and do the arithmetic there. Say which one you would pick and why.
The follow-up: so where do you do currency conversion, and at what precision? (There is no clean answer, which is exactly why it gets asked. They want to hear you reason about rounding direction and who absorbs the difference.)
6. The timestamp with no timezone
// schema
scheduledAt DateTime
// write
await db.reminder.create({
data: { scheduledAt: new Date("2026-03-29T02:30:00") },
});
Enter fullscreen mode Exit fullscreen mode
Asked: what could go wrong here?
Actually asked: do you know that an instant and a wall-clock time are different types of thing?
That string has no offset, so it is parsed as local time on whichever machine ran it. Your laptop and the container in Frankfurt disagree. And 02:30 on 29 March 2026 does not exist in most of Europe, because the clocks go from 02:00 straight to 03:00 that morning.
“Always store UTC” is the memorised answer and it is not always right. An instant, like when a payment settled, is UTC and converted for display. A future wall-clock commitment, like a 09:00 recurring standup, has to be stored as local time plus a zone id, because if the zone changes its rules between now and then, the UTC instant you computed is wrong and the 09:00 is still 09:00.
The follow-up: the meeting was booked before a country changed its DST rules. What is in your database now? (This is the whole question. It is why the answer is a zone id and not an offset.)
7. The check that loses a race
const existing = await db.user.findUnique({ where: { email } });
if (existing) return existing;
return db.user.create({ data: { email } });
Enter fullscreen mode Exit fullscreen mode
Asked: is this safe?
Actually asked: do you know that a read and a write are two separate moments?
Two requests arrive together. Both read, both find nothing, both insert. You now have duplicate users, and everything downstream that assumed one row per email is quietly wrong. It will not reproduce locally, and it will happen the first time a client retries on a timeout.
The answer that scores does not propose a longer if. It puts a unique constraint on the column and lets the database be the thing that arbitrates, then either catches the violation and re-reads, or uses an upsert. The database is the only place with a serialisation point. Application code cannot manufacture one.
The follow-up: what does your API return to the request that lost? (The existing row and a 200, most likely. Which is the same shape as an idempotency key, and that is usually where they take it next.)
The pattern, so you can do this yourself
Read those seven again and the second question is always one of a small number of shapes.
- One level down. You said what happens. Now say what makes it happen.
- What you were left holding. Not the control flow, the state after the failure.
- When your answer stops being true. Every rule has a boundary, and naming yours unprompted is the strongest single move available to you.
- What it cost. You picked something. Something else got worse.
You can run all four against any answer you would give, on any topic, without knowing what you will be asked. Wherever you run out of road on the second or third pass, that is a gap, and finding it tonight is much cheaper than finding it on a call.
The part that reading cannot fix
Here is the uncomfortable bit, and the reason lists like this one are less useful than they look.
Recognising the second question on a page you are reading calmly is not the same skill as producing the answer out loud, in order, in one breath, eleven minutes into an interview, immediately after someone has picked one phrase out of what you just said and asked you about that specifically.
Those are different skills. Only one of them gets tested. And the gap between them is not closed by reading more, because you cannot find the edge of your own knowledge by consulting it. You find it by being asked, and then being asked again about whatever you just said.
So: pick one topic you would claim on your CV. Say your answer out loud, to the wall if necessary. Then ask yourself the four shapes above, in order, and keep going until you run out. The place you stop is your interview.
I build PracticeDepth, an AI interviewer that asks senior-level questions across sixteen topics, then drills into whatever you just said until it finds your ceiling, and tells you where you actually stand. A practice run is free and takes about twenty minutes. It is a lot less comfortable than reading a list, which is the entire point.
If any of the seven above went a way you did not expect, I would genuinely like to hear it in the comments.
답글 남기기