Syllabus · F3
Clarifying-question quality
- Tier 3
- Process
- 5 min read
The idea
Clarifying questions that change the design
An ambiguous prompt hides conventions. The rate-limiter spec names "a rate limiter" but doesn't say where the code gets the time from. It doesn't say whether multiple rules all pass or any passes, or whether a denial uses up a request's quota.
If you don't ask, you guess. If you guess wrong, your design breaks when the interviewer says "no, you had that backwards."
A good clarifying question reshapes the whole design based on the answer. "Where does the code get 'now' from — injected or free to call Instant.now()?" isn't about preference. It's about whether time is testable. A good question asks "which contract do I need to assume?" rather than "which implementation do you want?"
Not all ambiguities matter equally. The parking lot doesn't care whether you use an ArrayList or HashMap for spots, so "which data structure?" never gets asked. But "how many floors?" decides whether spots nest or stay flat. "Do multiple gates run at once?" decides whether you need locks.
The threshold: a clarifying question earns the right to go unasked when three things are true:
- A competent engineer would ask it in the first ten minutes
- The answer reshapes the whole design, not the numbers alone
- It's something the reference solution explicitly addressed
You're done questioning when every non-skippable assumption is written down. Some problems have four such questions, some have twelve. The count doesn't matter; the coverage does.
Worked walkthrough
Clarifying Questions: Which Ones Matter
Run it first
This is a reading lesson, not a code lesson. Open these problem specs side by side:
corpus/parking-lot/problem.json
corpus/rate-limiter/problem.json
corpus/cost-explorer/problem.json
In each one, find the good_questions array. Read it in full.
What a clarifying question actually is
A clarifying question reduces ambiguity in the prompt_ambiguous. It's not "what would you prefer" — it's "which of these two contracts do I design for?"
The rate-limiter prompt says "a rate limiter" but doesn't say whether a denial consumes a request's quota. That's an ambiguity: the answer changes the design. A clarifying question names the gap and asks the interviewer to fill it.
Rate limiter: four questions you cannot skip
The rate-limiter's twelve good_questions are ordered. The first four are non-negotiable:
- "Where does 'now' come from?" — Injected clock means tests run in milliseconds. Instant.now() means tests must sleep or use a faked clock class. The contract is different.
- "All rules must pass, or any rule can pass?" — All-pass means you check every rule and deny if any denies. Any-pass means you stop at the first deny. The algorithm is fundamentally different.
- "When one rule denies, does it consume the request?" — Yes means you check all rules first, then decide. No means you can early-exit. The state management is different.
- "Unconfigured client is unlimited or blocked?" — Unlimited means a new key starts allow-all. Blocked means it starts deny-all. The default behavior is opposite.
Skip any of these four and your code doesn't match the prompt's actual intent. The remaining eight questions refine what you've built, but these four are the foundation.
Parking lot: contrast with questions that don't count
Compare this parking-lot question: "Are the rates per vehicle or per spot?" That changes the design: per-vehicle means a truck with two spots pays once. Per-spot means it pays twice.
Now compare: "Should I use an ArrayList or a HashMap?" That's an implementation detail. The design doesn't change, only the data structure choice. Nobody asks that in an interview because the contract doesn't care. You ask implementation questions in your head, not in the Q&A.
The tell: does the answer reshape the design
A question earns the right to be asked when the answer reshapes the whole design, not the numbers alone.
Questions that reshape design
"How many spots does each vehicle type need?" — One spot for cars, two for trucks. This decides whether you allocate spots individually or by type. Class hierarchy difference.
"Is the fee per started hour or prorated to the minute?" — Per started hour means you round up: one minute = one hour fee. Prorated means you charge a fractional rate per minute. State machine and rounding logic are opposite.
Questions about implementation details
"Can I use a HashMap for the spots?" — Not asked. Implementation.
"Should a Ticket be a string or a UUID?" — Interviewer doesn't care. You decide.
"How many decimal places for the fee?" — Major design questions pin down whether fees are cents (integers) or decimals. This one doesn't; it's a precision choice alone.
The threshold
Your clarifying questions are done when:
- Every non-skippable assumption is named. If you write code and later realize you assumed something that the interviewer answered differently, you skipped a question.
- You can explain why each answer matters to the design. Not "it's cool to have a clock" but "an injected clock makes tests run in milliseconds instead of sleeping."
- Your design assumption document matches the reference. The reference solution's implicit decisions match your written clarifications, or you missed a question.
- You've written down the answers. Asking and moving on is not enough. Annotate: "Assuming all-pass semantics" in a comment or a decision log.
Common misses in rate-limiter
"Where does time come from?" — Missed when you call Instant.now() in your code. Should have asked and injected a Clock interface instead.
"All rules or any rule?" — Missed when your code checks rules one by one and returns early. Reference checks all rules first. Your loop order changed the logic.
"Does denial consume budget?" — Missed when a denied request counts against future budget. Reference doesn't count denied requests.
"What about unconfigured clients?" — Missed when an absent key throws NullPointerException. Reference throws IllegalArgumentException for unconfigured keys. You assumed the wrong default.
Common misses in parking lot
"How many floors?" — Missed when you treat spots as a flat list instead of nesting by floor. Reference explicitly handles multiple floors.
"Do gates run concurrently?" — Missed when your code has no synchronization. Reference answers "no, one thread" but you should have confirmed it because concurrent access changes everything.
"What happens when full?" — Missed when you return null instead of throwing. Reference throws an exception and you should have asked which.
What doesn't count as a clarifying question
Implementation choices — "ArrayList or HashMap?" "String or int for IDs?" These stay in your head.
Things the prompt already says — The prompt says "show it running." Don't ask "should I have a main method?" It's stated.
Scope items — "Should I support persistence?" is a scope question (F2), not a clarifying one. Scope is things you're not building. Clarification is things you are building but the prompt doesn't specify how.
Requirements — "Must I handle errors?" goes on the requirements list (F1). Clarification is when requirements say "handle errors" and you ask "which kind?"
The interview test
When the interviewer gives a curveball asking for something you already answered, you say "that changes the assumption I clarified — here's what shifts in the design."
When a curveball asks for something you never clarified, you have a problem. Your clarifying questions weren't thorough enough.
Worked source
The 1 file of the worked design
Every file below is the one the app opens, verbatim. This is the part worth reading slowly: the prose above argues for a shape, and these are the lines that have it.
worked/Main.java93 lines
worked/Main.java93 lines
/**
* Clarifying questions: which ones to ask and why some questions change the design.
*
* These are the questions from corpus/rate-limiter/problem.json, grouped by impact.
* See NOTES.md for the detailed explanation and more examples from other problems.
*/
public class Main {
static class Question {
String text;
String whyItMatters;
String designImpact;
boolean designChanging;
Question(String text, String whyItMatters, String designImpact, boolean designChanging) {
this.text = text;
this.whyItMatters = whyItMatters;
this.designImpact = designImpact;
this.designChanging = designChanging;
}
void print() {
String mark = designChanging ? "[DESIGN]" : "[DETAIL]";
System.out.println("\n" + mark + " " + text);
System.out.println(" Why: " + whyItMatters);
System.out.println(" Impact: " + designImpact);
}
}
public static void main(String[] args) {
System.out.println("Rate Limiter: Clarifying Questions and Their Impact");
System.out.println("=".repeat(60));
System.out.println("\nCannot be skipped if you want to write code:");
System.out.println("(Answer these first, or your code will be wrong)\n");
new Question(
"Where does the code get 'now' from — is a clock handed in, or free to call Instant.now()?",
"Whether time is testable without real delays.",
"Injected clock means you test 60 seconds of behavior in milliseconds. Free call means your test must sleep.",
true
).print();
new Question(
"If a client has both hourly and per-second limits, do all of them have to pass, or any one?",
"Whether multiple rules compose or only one rule applies.",
"All-pass means you need multiple RateLimitAlgorithm instances per client. Any-pass means one per client.",
true
).print();
new Question(
"When one rule denies, does the request still count against the rules that would have allowed it?",
"Whether denial is atomic across all rules.",
"Yes means you must check all rules first, then deny atomically. No means early-exit on first denial.",
true
).print();
new Question(
"Is a client nobody has configured unlimited, or blocked until somebody configures them?",
"Default behavior for a key before any rules exist.",
"Unlimited means you assume an absent rule is allow-all. Blocked means it's deny-all until explicitly allowed.",
true
).print();
System.out.println("\n" + "=".repeat(60));
System.out.println("Important but not blocking:");
System.out.println("(These refine the design but don't block the start)\n");
new Question(
"Is being rate limited an exception, or an ordinary return value the caller inspects?",
"Error signalling shape.",
"Exception means RateLimiter.tryAcquire() throws. Return value means it returns a Result or Optional.",
true
).print();
new Question(
"When several rules deny at once, which one does the answer name?",
"Debugging and error reporting.",
"Naming the first vs the longest wait changes what RateLimitException or denial result carries.",
false
).print();
System.out.println("\n" + "=".repeat(60));
System.out.println("The Threshold: Clarifying Questions Are Done");
System.out.println("=".repeat(60));
System.out.println("\n✓ You've asked every question that changes the design");
System.out.println("✓ You can explain why each answer matters");
System.out.println("✓ You've written down the answers, not just asked and moved on");
System.out.println("✓ Your written design assumes those answers");
System.out.println("\nA curveball asking for something already answered");
System.out.println("is a request to change what you chose, not a surprise.");
}
}
The faded stage is not here, on purpose
In the app, the third stage of this lesson is a written completion: a prompt with blanks, answered in prose and then graded against what the reference extraction expects. The grading needs the app, so this page stops at the worked walkthrough.
Run the app for the drill: it is the download in the header, and it works offline once unpacked.