Module 1: How to Approach a System Design Problem
3. Asking the right clarifying questions
Description
You already know that a design prompt arrives vague on purpose and that what decides the architecture are the non-functional requirements. Now comes the practical question: how do you get those requirements if nobody gave them to you? The answer is this lesson's skill: asking. Before drawing a single box, you interrogate the prompt until you turn its holes into numbers. You don't ask at random —"do they use the cloud?"— but with method: there are a few families of questions that, once answered, hand you almost everything you need to design. Asking well is not a courtesy formality; it's the part of the work where the design is won or lost, because a single answer can multiply the scale by a hundred and send you to a completely different architecture.
And when there's no one to ask —you work alone, the prompt is all there is, or you're in an interview and the interviewer bounces the ball back—, the skill has a second half: stating an assumption. Instead of freezing, you say out loud "I'll assume 100 million URLs a month, 100:1 ratio; if it were different, the design would change like this". Writing the assumption is itself an act of design: it makes explicit the number everything else hangs on, so anyone can review it and, if it's wrong, correct course early. A design without stated assumptions is a design that hides what it depends on.
Connection to the module: this lesson is the machine that produces the raw material of the method. Lesson 2 taught you to recognize the non-functional requirements; this one teaches you to obtain them. Each family of questions points directly at a family of non-functionals: "how many users?" produces the throughput, "how fast?" produces the latency, "how long does the data live?" produces the storage. What comes out of here —requirements with numbers and stated assumptions— is exactly what lesson 4 will scope and what lesson 5 will put as the first step of the framework. Without questions, there are no requirements; without requirements, there's no design.
The doctor who doesn't prescribe before asking
Think of it this way. You go to the doctor and say: "Doctor, my head hurts." A dangerous doctor prescribes you a painkiller right away. A good doctor, before prescribing anything, asks: Since when? Where exactly? Is it sharp or dull? What eases it, what makes it worse? Are you taking any medication? Have you had a fever? None of those questions is a treatment yet. They're a directed interrogation: each question rules out or confirms possibilities, until the space of diagnoses shrinks to a treatable one. The doctor doesn't ask at random ("do you like soccer?"); they ask what changes the diagnosis. And they know the same complaint —"my head hurts"— can be dehydration or something serious, and that the difference is in the answers, not in the complaint.
Notice two things about that interrogation. First, it's efficient: the doctor doesn't ask a hundred questions, they ask the ten that most reduce uncertainty. They have a mental repertoire of "what to ask for a headache", honed by experience. Second, when a piece of data is missing that they can't obtain on the spot —there's no blood test at hand—, the doctor works with an explicit hypothesis: "assuming there's no fever, we start with hydration; if a fever shows up, we change the plan". They don't freeze for lack of data: they assume with judgment and make clear what their decision depends on.
A system designer facing a vague prompt is that doctor facing "my head hurts". The prompt —"design a URL shortener"— is the complaint. Your directed interrogation is the families of questions of this lesson: the few that really change the design. And when there's no one to ask, you do what the good doctor does: you assume with judgment and state the assumption. Asking at random ("AWS or Google Cloud?") is like asking the patient if they like soccer: it doesn't change the diagnosis. Asking "how many URLs a month?" is asking about the fever: it changes everything.
It's worth spelling it out in full:
Asking is a directed interrogation, not a chat: a few families of questions produce the numbers that decide the design. And when there's no one to ask, don't freeze: state an explicit assumption and keep going.
The five families of questions
You don't have to memorize hundreds of questions. There are five families, and out of each come the two or three questions that almost always matter. Run through them in order and you'll have covered what decides a design.
1. Users and scale (the family that rules most)
It's the first because it produces the numbers that dominate everything else. Typical questions:
- How many users? How many operations per month/day?
- Is the load read-heavy or write-heavy? What's the read:write ratio?
- Is the traffic uniform or does it have peaks (rush hour, viral)?
For Enlace, the answers are the assumptions you already know: 100 million new URLs a month, 100:1 ratio (read-heavy), and —we assume it— moderate peaks. This family alone already told you Enlace is read-heavy, which is the observation that organizes half the design.
2. Operations (the functional requirements)
What the system has to do, at its core:
- What are the main operations? (For Enlace:
shorten,resolve.) - Are there secondary operations? (Edit a link? Delete it? List mine?)
- What happens in the edge cases? (Nonexistent code → 404? Invalid URL → error?)
This family produces the functionals. It's usually the fastest, because the core of almost any system is two or three operations.
3. Data and its lifecycle (the storage)
What is stored, how much it weighs, how long it lives:
- What central entity is stored? (For Enlace: the
Linkrecord.) - How much does each record weigh? (~1 KB for Enlace.)
- How long is it retained? Does the data expire? (Retention: 5 years; expiration: optional.)
This family produces the storage requirement —Enlace's 6 TB come from here— and feeds the estimation of module 2 directly.
4. Constraints and quality (the rest of the non-functionals)
The limits and the guarantees:
- Latency: how fast does it have to respond? (< 100 ms for the redirect.)
- Availability: how many nines? (99.9% for Enlace.)
- Consistency: does a just-written piece of data have to be seen immediately everywhere, or can it take a moment? (For Enlace, it can take a moment: eventual consistency.)
- Durability: can a piece of data be lost? (For Enlace, never: a lost link is broken forever.)
5. What NOT (the scope)
The family almost everyone forgets, and the most valuable for not drowning:
- What's out of scope? (Authentication? Billing? Analytics dashboard?)
- What is "for later" (v2) and what is "now" (v1)?
- Are there features that sound mandatory but are actually optional? (In Enlace: click analytics, custom URLs, expiration.)
This family is the bridge to lesson 4, which is devoted entirely to scoping. Asking "what NOT?" gives you explicit permission to leave things out, instead of trying to design everything and finishing nothing.
Laid out on a map, the five families and what they produce:
QUESTION FAMILY -> WHAT IT PRODUCES
1. Users and scale -> throughput (QPS), peaks [non-functional]
2. Operations -> functional requirements
3. Data and lifecycle -> storage, retention [non-functional]
4. Constraints and quality -> latency, availability, [non-functional]
consistency, durability
5. What NOT -> scope (in / out)
Notice that four of the five families produce non-functional or scope requirements. Only one produces the functionals. That tells you again where the real work is: in pulling numbers and limits out of the prompt, not in enumerating features.
Worked example: an answer that multiplies the system by a hundred
Let's see why asking about scale before anything else isn't a whim. Imagine Enlace's prompt doesn't come with the "100 million a month", and you ask the family-1 question: "How many new URLs do we expect a month?". The owner wavers between two answers —"a million? a hundred million?"—. It looks like a detail. Let's compute what each answer does to the design:
# the impact of ONE answer from the "scale" family
seconds_per_month = 30 * 24 * 3600
ratio = 100 # read:write
for label, writes_per_month in [("1 million/month", 1_000_000),
("100 million/month", 100_000_000)]:
qps_write = writes_per_month / seconds_per_month
qps_read = qps_write * ratio
print(f"{label:18}: {qps_write:7.2f} writes/s | {qps_read:8.1f} reads/s")
print(f"\nfactor between the two scenarios: {100_000_000 / 1_000_000:.0f}x")
What to expect. Running this with Python 3.14.0:
1 million/month : 0.39 writes/s | 38.6 reads/s
100 million/month : 38.58 writes/s | 3858.0 reads/s
factor between the two scenarios: 100x
Pause on what it means. At 1 million a month, Enlace does 0.4 writes/s and 39 reads/s: that fits comfortably on a single modest machine, no cache, no replicas, no load balancer. At 100 million a month, it does 39 writes/s and ~3858 reads/s: that already calls for a cache on the read path, and starts to justify replicas and balancing. The same question, two answers, and the design jumps from "one box" to "a system with cache and replicas" —a factor of 100 in load—. That's why the "users and scale" family goes first: it's the one that most reduces uncertainty, like the fever for the doctor. If you could only ask one question before designing, it would be this one.
And notice the corollary: if the prompt doesn't give you this number and there's no one to ask, you can't design anything defensible until you state it as an assumption. "I assume 100 million a month" is not optional; it's the nail everything in the design hangs on. Without it, any box you draw floats in the air.
When there's no one to ask: stating assumptions
In many scenarios —an interview, an exercise, a project where you're the one deciding— there's no owner to answer you. There the skill isn't to stop asking, it's to make your answer explicit. The correct form has three parts: (1) the number you assume, (2) why it's reasonable, (3) how the design would change if you were wrong. Example, for Enlace:
ASSUMPTION 1: 100 million new URLs/month.
Why: the scale of a known public shortener (order of magnitude of a
large service, not an intranet).
If I'm wrong: if it were 1M/month, a single box would be plenty and there'd
be no need for cache or replicas; if it were 10,000M/month, it
would need much more sharding than the guide plans.
ASSUMPTION 2: read:write ratio = 100:1 (read-heavy system).
Why: people visit a link many more times than they create it.
If I'm wrong: if it were 1:1, the cache would pay off much less and the
design would look more like a balanced system.
Notice the power of writing it this way. Anyone who reads your design can, in ten seconds, see what it depends on and challenge it: "actually we expect 10 billion a month" —and then you know exactly what to recompute—. A stated assumption turns a fragile design ("it works if the numbers are the ones I silently imagined") into a robust one ("it works under these explicit assumptions, and here's the plan if they change"). In an interview, stating assumptions out loud is the clearest sign of design maturity: it shows you know the design depends on numbers, and that you didn't invent them at random.
There's a trap to avoid: don't confuse an assumption with an excuse not to think. "I assume everything works" isn't an assumption, it's giving up. A good assumption is specific (a number), justified (a reason), and consequential (you say what changes if it's false). If your assumption doesn't have all three, it's not yet a design assumption.
Common mistakes
Asking implementation details instead of requirements. What happens: someone, the moment they get the prompt, asks "do we use PostgreSQL or MongoDB?", "AWS or Google Cloud?", "REST or gRPC?". Those are how questions, and you don't even know what yet. It's like the doctor asking the brand of the painkiller before knowing what hurts. Why it happens: technical questions feel expert and are comfortable for someone who already knows tools. How to detect it: if your question names a specific technology, it's almost certainly premature. How to fix it: stay at the requirements level —how many, how fast, what's stored, what NOT— until you have the high-level design. The technology is chosen in the "deep dive" step of the framework (lesson 5), not at the start.
Not asking about the scale and assuming it silently. What happens: someone starts designing without setting how many operations a month, imagining in their head a number they never say. They design for "many users" without defining how many, and end up with an architecture that may be ten times too big or ten times too small for the real problem. Why it happens: the scale number feels obvious and gets omitted. How to detect it: if you can't say out loud "I'm designing for X operations/s", you didn't set the scale. How to fix it: the scale is the first question (family 1) and, if there's no answer, the first stated assumption. Never design on a number imagined in secret.
Forgetting the "what NOT" family. What happens: someone tries to design everything the prompt hints at —analytics, user accounts, custom URLs, expiration, rate limits— and drowns before finishing the core. A design that tries to cover everything covers nothing well. Why it happens: it feels risky to leave things out, as if it were admitting you don't know how to do them. How to detect it: if your feature list doesn't have an explicit "out of scope" section, you're missing family 5. How to fix it: actively ask "what do I NOT have to solve now?" and write it down. Leaving something out on purpose is design; leaving it out by oversight is a hole. Lesson 4 turns it into a method.
Exercises
Exercise 1 — Classify the questions by family. You have to design Enlace and you prepared this list of questions. Assign each one to its family (1: users/scale, 2: operations, 3: data/lifecycle, 4: constraints/quality, 5: what NOT) and mark which is premature (implementation, not requirements): (a) "How many redirects do we expect per second at peak hour?" (b) "Do links expire?" (c) "Do we use Redis or Memcached for the cache?" (d) "Do we need a just-created link to work instantly worldwide?" (e) "Do we have to support deleting a link?" (f) "Does click analytics go in v1 or do we leave it for later?"
See solution
- (a) Family 1 (users/scale). Asks about read throughput and peaks. Good and top priority.
- (b) Family 3 (data/lifecycle). The
Link's lifecycle: does it haveexpires_at? Feeds storage and retention. - (c) Premature (implementation). Choosing Redis vs. Memcached is a how decision, from the "deep dive" step. You don't even know yet whether you need a cache. Save it.
- (d) Family 4 (constraints/quality). Asks about consistency: strong (instantly worldwide) or eventual (can take a moment)? For Enlace, eventual is enough.
- (e) Family 2 (operations). A secondary operation: does
deleteexist? Defines the functional contract. - (f) Family 5 (what NOT). A scope question: is analytics v1 or v2? It's exactly the question that avoids drowning.
The lesson: five of the six are good requirements questions, each from a different family —complete coverage—. Only (c) is premature, because it names a technology before having the design. Recognizing the premature one is as important as asking the good ones.
Exercise 2 — Write a stated assumption. There's no owner to ask. You have to set the latency of Enlace's redirect. Write a complete stated assumption, with its three parts: (1) the number you assume, (2) why it's reasonable, (3) how the design would change if you were very wrong (for example, if the real target were 10 times stricter).
See solution
A well-formed stated assumption:
ASSUMPTION: the redirect responds in < 100 ms (perceived by the user).
Why: below ~100 ms a redirect feels instantaneous; the user can't tell
20 ms from 80 ms, so 100 ms is a comfortable and realistic target
for a shortener.
If I'm wrong (real target < 10 ms): 100 ms is met by an indexed DB with
no problem, but < 10 ms consistently would require serving almost
everything from an in-memory cache and, maybe, bringing the servers
closer to the user (CDN / points of presence). The design would gain
a much more aggressive cache layer and geo-distribution, which the
100 ms version doesn't need.
Notice that the third part is the most valuable: it says what changes in the design if the number is different (from "an indexed DB is enough" to "aggressive cache + geo-distribution"). That turns the assumption into a design tool, not a filler sentence. An assumption without the "if I'm wrong" part is just a guess with nice handwriting.
Exercise 3 — The question that most reduces uncertainty. You're given a new prompt: "Design a link-shortening service for a restaurant chain: each branch generates short QR codes for its menus." You can only ask one question before starting to design. Which do you ask, which family is it from, and why that one and not another? Also estimate, with the answer you imagine, whether this looks more like the public Enlace (needs cache/replicas) or the intranet Enlace (a single box).
See solution
The highest-impact question is from family 1 (users/scale): "How many branches are there and how many times a month is each QR scanned?" —that is, the throughput—. Why that one: as we saw in the worked example, scale is the variable that moves the design most (factor 100 between 1M and 100M a month); no other answer reorders the architecture as much. Asking about technology, lifecycle, or even operations would give you less information about what size of system to build.
Estimation with an imagined answer: suppose a chain of 200 branches, each QR scanned ~500 times a day (a menu is seen by many diners):
- Reads/day = 200 × 500 = 100,000 → ~1.2 reads/s. Writes (creating QRs): a handful per branch a month, negligible.
At ~1 read/s, this looks much more like the intranet Enlace: a single box resolves it with enormous margin, no cache, no replicas, no load balancer. The same question that in the public Enlace gave "I need cache and replicas", here gives "one box is enough" —because the scale answer is a thousand times smaller—. That's the whole lesson: a well-chosen question (the scale one) tells you, by itself, which half of the design map you're standing on.
Summary and next step
In this lesson you learned to obtain the requirements that lesson 2 taught you to recognize: by asking with method, like the doctor who interrogates before prescribing. The five families —users/scale, operations, data/lifecycle, constraints/quality, and what NOT— together produce the functionals, the non-functionals, and the scope; and four of the five produce non-functionals or limits, which is where the real design lives. The scale family goes first because it's the one that most reduces uncertainty: a single answer can multiply the load by a hundred.
And you learned what to do when there's no one to ask: state an assumption —number, justification, and what changes if it's false—. A stated assumption turns a design that hides what it depends on into one that puts it on the table for anyone to review. The trap to avoid is the vague assumption ("I assume it works"): the good one is specific, justified, and consequential.
Before moving on you should be able to: recite the five families and what each produces; identify which question is premature (implementation) and which is requirements; write a stated assumption with its three parts; and explain why the scale question goes first.
What comes next is using all this to make a decision almost nobody makes explicitly: what to leave out. You already know how to ask "what NOT?"; lesson 4 turns that question into a method to scope —choose what goes into the first design and what gets deferred—, with the example that proves it with numbers: how adding click analytics multiplies Enlace's write load by a hundred.
Resources
- System Design Primer — "Step 1: Outline use cases, constraints, and assumptions" — the first step of the Primer's method is, literally, this lesson: clarify use cases, constraints, and assumptions before designing. Compare it with the five families; it's the same directed interrogation, said another way.
- Designing Data-Intensive Applications (DDIA), Chapter 1 — official site — Kleppmann's "Describing Load" section shows how a system's load is characterized with "load parameters" (requests/s, read:write ratio, simultaneous users). It's exactly what the "users and scale" question family produces.
- Google SRE Book — "Embracing Risk" — why you don't ask for "the maximum" availability but the level the business needs, and how it's decided with judgment. It gives the foundation for why the "constraints" family questions seek a justified number, not an ideal.