How to Spend the 45 Minutes
A budget that works: 5 clarifying, 5 estimating, 10 high-level, 15 deep dive, 10 bottlenecks — and what an overrun costs.
5 clarify -> 5 estimate -> 10 high-level -> 15 deep dive -> 10 bottlenecks (45 total)Forty-five minutes is short enough that the budget is part of the answer. A split that works: 5 minutes clarifying, 5 estimating, 10 on the high-level design, 15 on whichever deep dive the interviewer picks, 10 on bottlenecks and follow-ups.
| Phase | Minutes | Done when |
|---|---|---|
| Clarify | 5 | Scope is written where you both can see it, and agreed |
| Estimate | 5 | Read QPS, write QPS, storage per year |
| High level | 10 | One labelled path from client to datastore and back |
| Deep dive | 15 | The component they chose, down to keys and failure |
| Bottlenecks | 10 | Single points of failure named, next steps stated |
Where the time actually goes
The deep dive carries the depth signal, and it is the phase that gets eaten. Ten extra minutes of requirements gathering feels productive — the questions are still sensible ones — but it is paid for out of minute 30 onwards, so the interviewer never watches you take anything to the bottom. Overrunning the high-level design does the same damage.
Say the transitions out loud
Keep a visible clock and announce each move: "that is the scope, two minutes on numbers now." It gives the interviewer an opening to redirect you early, which is almost always in your favour — they already know which deep dive they intend to ask for, and will often steer you into it if you pause.
If you are late, compress deliberately rather than continuing in order: lock the scope, keep one number, draw four boxes, and ask which part they want the remaining time spent on.
Example
// The budget as data, so "where should I be right now?" has an answer.
const PHASES = [
{ name: 'clarify', minutes: 5, done: 'scope written down and agreed' },
{ name: 'estimate', minutes: 5, done: 'read QPS, write QPS, storage per year' },
{ name: 'high-level', minutes: 10, done: 'four boxes, labelled arrows, one request walked' },
{ name: 'deep-dive', minutes: 15, done: 'their chosen component, to keys and failure' },
{ name: 'bottlenecks', minutes: 10, done: 'SPOFs named, next steps stated' },
];
// Cumulative deadline per phase.
// Longhand: let t = 0; const schedule = []; for (const p of PHASES) { t += p.minutes;
// schedule.push(Object.assign({}, p, { endsAtMinute: t })); }
// Shorthand below: { ...p } is object spread (ES2018), identical to Object.assign({}, p).
let elapsed = 0;
const schedule = PHASES.map((p) => ({ ...p, endsAtMinute: (elapsed += p.minutes) }));
// ?? is nullish coalescing (ES2020); Array.prototype.at is ES2022.
const phaseAt = (minute) => schedule.find((p) => minute < p.endsAtMinute) ?? schedule.at(-1);
phaseAt(12).name; // 'high-level' — if you are still clarifying here, you are behindWhen to use it
- Pacing a 45-minute loop so the deep dive is not cut short
- Running a timed mock with a peer and scoring the pacing separately from the design
- Recovering when requirements gathering has already taken twelve minutes
- Deciding what to cut when the interviewer joins five minutes late
More examples
Recovering from an overrun
Handing the interviewer the choice of deep dive turns a pacing mistake into a collaboration, and they will pick the component they were going to ask about anyway. Use it the moment you notice you are more than five minutes behind.
// Minute 17 and the scope is still being negotiated. Do not continue in order.
const recover = (minutesLeft) => [
'name it: "I have spent too long on requirements, locking the scope now"',
'keep ONE number: the read:write ratio. Drop storage and bandwidth maths',
'draw four boxes only — client, load balancer, service, datastore — and walk one request',
// Template literal; the longhand is '...with the remaining ' + minutesLeft + ' minutes?'
`ask directly: "which part should I go deep on with the remaining ${minutesLeft} minutes?"`,
];
recover(28);What each phase must leave on the board
Timing advice is hard to act on; a required artefact is not. Run this list over a photograph of your own practice whiteboard and the pacing problems show up as blank cells.
// Artefacts, not activities. If a phase ends with nothing written, it did not happen.
const artefacts = {
clarify: ['in scope', 'out of scope', 'the numbers you were given'],
estimate: ['read QPS', 'write QPS', 'bytes per record x records per year'],
highLevel: ['boxes', 'arrows labelled with protocol and sync/async', 'the happy path'],
deepDive: ['the key or partition key', 'the index', 'what happens when this box dies'],
bottlenecks: ['single points of failure', 'the first thing you would measure in production'],
};
// A quick self-check at the end of a mock round.
const missing = (board) =>
Object.entries(artefacts).flatMap(([phase, items]) =>
items.filter((i) => !board.includes(i)).map((i) => `${phase}: ${i}`));
Discussion