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.

Syntaxread: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

QuestionWhat 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

Example · javascript
// 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.

Example · javascript
// "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.

Example · javascript
// 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

  • Be the first to comment on this lesson.