Clarifying the Ask
Which questions change the design and which just spend the clock — and why the agreed scope belongs on the board in writing.
read:write ratio? | staleness budget? | what must never be lost? | how many users? | one region or global?The ask is vague on purpose, so clarifying is not politeness: it is the phase that decides which design is correct. But only some questions move a box on the board, and the rest spend a clock you cannot get back.
Questions that change the design
| Question | What the answer decides |
|---|---|
| Read-heavy or write-heavy, and by roughly what ratio? | Precompute on write versus compute on read |
| How stale may this data be? | Cache TTLs, async replication, serving from replicas |
| What must never be lost? | Synchronous replication before the ack, versus fire and forget |
| How many users, and how active is each one? | Whether one database is still the answer |
| One region or global? | Multi-region, and the consistency bill that comes with it |
Questions that waste the minute
Which cloud, which language, which ORM, whether a managed queue is allowed. None of them change the shape of the design. Four or five questions is a complete clarifying phase; after that you are stalling, and it reads that way.
Write the scope down
Put it in a corner of the board and leave it there: in scope, out of scope, and the numbers you were handed. It is a contract in both directions. When minute 35 brings "what about search?" you point at out of scope: search and the answer takes five seconds instead of derailing the deep dive. It also catches the opposite failure — asking good questions and then designing as if the answers had been different.
Example
// The clarifying phase, as the artefact it should produce. Every field here
// changed at least one box; questions whose answers changed nothing were skipped.
const agreed = {
// 1_000_000 === 1000000 (numeric separators, ES2021)
scale: { dau: 1_000_000, newPostsPerDay: 2_000_000 },
shape: 'read-heavy, roughly 50 reads per write', // -> precompute the feed on write
staleness: {
feedSeconds: 30, // a 30s-old feed is acceptable -> serve from cache and replicas
balanceSeconds: 0, // a balance is not -> read inside the transaction, on the primary
},
mustNotLose: ['payments', 'uploaded originals'], // -> synchronous replication before the 200
mayLose: ['view counters', 'recommendation logs'], // -> batched, async, best effort
availability: 0.999, // 43.2 minutes of downtime per 30 days
regions: ['us-east'], // single region agreed, so no cross-region consistency problem today
outOfScope: ['search', 'moderation tools', 'analytics UI', 'mobile offline mode'],
};
// Re-read this before the deep dive. A design that contradicts a field here is
// a design the interviewer already told you was wrong.
const contradicts = (design) =>
design.feedBuiltOnRead && agreed.shape.startsWith('read-heavy');When to use it
- Turning "design Instagram" into a scope you can actually finish in 45 minutes
- Deciding which four questions to ask when you only have five minutes
- Closing down an out-of-scope follow-up at minute 35 without losing the thread
- Writing the assumptions section of a design doc before any diagram exists
More examples
Staleness, answer by answer
Asking about staleness per data type, rather than once for the whole system, is what lets you justify a cache for the feed and refuse one for money in the same design. Reach for it before you draw any cache.
// "How stale may it be?" is one question per data type, and each answer
// licenses a specific mechanism. This table is the whole caching design.
const staleness = {
followerCount: { maxSeconds: 60, mechanism: 'cached counter, refreshed asynchronously' },
homeTimeline: { maxSeconds: 30, mechanism: 'precomputed on write, served from cache' },
searchIndex: { maxSeconds: 300, mechanism: 'batch indexer, queue between writes and index' },
accountBalance: { maxSeconds: 0, mechanism: 'read from the primary inside the transaction' },
};
// Anything with maxSeconds > 0 may be served from a replica or a cache.
const cacheable = Object.entries(staleness)
.filter(([, v]) => v.maxSeconds > 0)
.map(([name]) => name); // ['followerCount', 'homeTimeline', 'searchIndex']Questions that do not earn their minute
Permission-seeking questions read as risk aversion and cost you the same minute as a useful one. Use the reframes when you catch yourself about to ask whether something is allowed.
// Each of these gets asked constantly, and none of them moves a box.
const lowValue = [
{ q: 'Which cloud provider?', why: 'every provider has a load balancer, a blob store and a queue' },
{ q: 'Which language?', why: 'the service tier is stateless either way' },
{ q: 'Can I use Redis?', why: 'the real question is whether a cache is justified at all' },
{ q: 'Should it be microservices?',why: 'that is your decision to make and defend, not theirs to grant' },
];
// The reframe: ask for the number that decides, not for permission.
const reframed = {
'Can I use Redis?': 'What is the read:write ratio, and what is the p99 target for a feed load?',
'Should it be microservices?': 'Which parts scale independently, and who deploys them?',
};
Discussion