Syllabus · F4
Decision defence and trade-off articulation
- Tier 3
- Process
- 5 min read
The idea
Decision defence — articulating the choice you made
In an interview, you'll say "I put the algorithm behind an interface" or "I held the lock across both checks" or "I use a double, not a fraction." The interviewer's next move is always the same: "Why not do it the other way?" That question is the whole interview. A decision you can't defend isn't a decision, it's a guess.
A defence has three parts. First: name the alternative you rejected. "I could have put the logic inside the limiter instead of behind a separate interface." Second: name the cost of that choice. "With the logic inside, adding a third algorithm would mean editing the limiter's code in multiple places." Third: explain why you chose your way instead. "By putting each algorithm behind an interface, a new one is a new class, zero edits to existing code."
You'll hear this in a curveball. The interviewer asks for something that wasn't in the scope you negotiated. If you made the right structural choices, the new requirement lands cleanly behind an existing seam. It costs one file, maybe ten lines. If you didn't think about the trade-off, you're retrofitting, and it costs thirty lines across three files.
The threshold: before you write code, name one thing you could do but won't. Then explain why. That's not over-thinking — that's the entire skill.
Worked walkthrough
Decision Defence: Reading the Reference Solution
Run it first
Find these reference implementations and look for how decisions are documented:
corpus/rate-limiter/reference/src/
corpus/cost-explorer/reference/src/
corpus/parking-lot/reference/src/
In each one, find comments or documentation that explain why a design choice was made, not only what it does.
What a decision defence looks like
A strong defence has this shape, written as the interviewer would ask it:
Question: "Why hold a ReentrantLock per key instead of one global lock?"
Decision: One lock per key, not a global lock.
What it anticipates: If the limiter becomes a bottleneck, a global lock makes every request wait for every other request to finish, even if they're different clients. Separate locks let two different keys run at the same time.
Cost without it: A global lock is simpler to write — no risk of deadlock, easier to reason about atomicity. But under load, throughput drops to zero. One request waits for every other one, and in a high-concurrency scenario, that is exactly the problem the rate limiter was installed to prevent.
Real example: Rate Limiter
In rate-limiter/reference/src, the decision about Algorithm and AlgorithmFactory:
Question: "Why is there an AlgorithmFactory at all? Why not construct the right algorithm directly in the rule?"
Decision: AlgorithmFactory is a separate seam from RateLimitAlgorithm. The factory decides which class implements which algorithm; the interface decides how that class behaves.
What it anticipates: When the interviewer asks "add a sliding-window algorithm," the answer is one new class plus one line in the factory — the registration line. Without the factory, adding a new algorithm means editing KeyBudget, the place where rules meet locks. That's the class with the most invariants to break.
Cost without it: It's tempting to put the algorithm selection inside KeyBudget as a switch statement. That works for two algorithms. For a third, you edit the switch, retest the whole class, and risk changing the locking logic by accident. The factory-as-data approach uses the rule's constant to look up its implementation; a new algorithm is a new registration line, never an edit to the limiter's core logic.
Another angle: Parking Lot
In parking-lot, the decision about storing spots in a flat array:
Question: "Why not a tree or a hash of levels, since a lot can have multiple floors?"
Decision: All spots in one flat array. No nesting, no per-level structure.
What it anticipates: A parking lot in the first version is one floor. You can query by spot number, add and remove spots, but there's no vertical nesting. Simplicity is the win.
Cost without it: If you build a tree structure for multi-floor support from the start, you're paying the cost of traversal, level-specific logic, and per-floor checking before you've heard "curveball." The flat array works perfectly for one floor. If the interviewer adds floors later, that's when you refactor. You've already shown that handling what the problem actually asks beats speculating on what it might ask.
The pattern
Every decision defence you write has this structure:
- The question — what the interviewer asks, or what you ask yourself at the whiteboard
- The decision — what you chose
- The anticipation — what problem this choice solves or what future change it makes easier
- The cost — what you gave up by choosing this way, and what you would lose without it
The entries that carry the most weight are the ones that defend a refusal. Why no concurrency in parking lot? Why no persistence? Why no metrics in rate-limiter? A decision to do something is easier to defend than a decision not to. The "why not" is what separates a candidate who thought about trade-offs from one who coded the happy path only.
How to practise
Read the reference solution for a problem. Find one architectural choice — an interface, a data structure, a separation of concerns. Ask yourself: "Why this way and not the obvious alternative?" Write down the question, the decision, what it anticipates, and what it would cost without it. Then check if the reference's own comments or documentation backs up your reasoning.
The goal is not to memorize answers, but to practise the shape of the question. When the interviewer says "add metrics to the rate limiter," you should not panic. You should say: "Metrics are out of scope as we defined it. Adding them without a seam means retrofitting logging into the decision logic here — that's 20 lines across multiple places. If metrics were in scope from the start, we'd have a separate interface, and this costs one new file."
That's not defended from a memorized answer. It's defended from understanding the trade-off you made at the whiteboard.
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.java133 lines
worked/Main.java133 lines
/**
* Decision Defence: Real Examples from LLD Interviews
*
* This program walks through how to articulate decisions at the whiteboard.
* See NOTES.md for the full explanation and how to read these from reference solutions.
*/
public class Main {
static class DecisionDefence {
String question;
String decision;
String anticipates;
String cost;
DecisionDefence(String q, String d, String a, String c) {
this.question = q;
this.decision = d;
this.anticipates = a;
this.cost = c;
}
void print() {
System.out.println("\nQ: " + question);
System.out.println("A: " + decision);
System.out.println(" Anticipates: " + anticipates);
System.out.println(" Cost: " + cost);
}
}
public static void main(String[] args) {
System.out.println("Decision Defence: Articulating Trade-offs");
System.out.println("=".repeat(70));
System.out.println("\nThree real decisions from the corpus, with their defences:");
new DecisionDefence(
"Why is the rate limiter's Algorithm behind an interface?",
"Each counting algorithm (fixed window, token bucket) is a separate class",
"When the interviewer asks for a third algorithm, we add a new class and one " +
"registration line. The limiter's logic is untouched.",
"Without the interface, adding a third algorithm means editing the class that " +
"holds the lock and the all-or-nothing commit. That's the highest-risk place in " +
"the code to edit."
).print();
new DecisionDefence(
"Why hold one lock per rate limiter key, not a global lock?",
"Each key's budget gets its own ReentrantLock. Threads on different keys " +
"don't contend.",
"If one key gets hammered with requests, it doesn't block another key's traffic. " +
"You're not replacing the problem you were hired to fix (overload) with a " +
"different one (lock contention).",
"A global lock is simpler to write and easier to reason about. Under load, " +
"every request waits for every other request. The limiter becomes the bottleneck."
).print();
new DecisionDefence(
"Why does Cost Explorer use integer arithmetic for money, not doubles?",
"Money is stored as minor units (cents): a long, never a double.",
"Proration divides a monthly price by the day count of that month. With a double, " +
"one-third of a cent is inexact. The remainder is lost to rounding. With integers, " +
"the remainder is visible and the requirement 'customer absorbs the remainder' " +
"becomes mechanical.",
"Doubles would be shorter to write. You'd avoid rounding logic. But you'd lose " +
"the ability to enforce a stated rounding convention, and you'd discover the bug " +
"in production when a customer's refund is off by a penny."
).print();
System.out.println("\n" + "=".repeat(70));
System.out.println("The Pattern Every Defence Follows");
System.out.println("=".repeat(70));
System.out.println("\n1. The interviewer asks a question: 'Why did you do X?'");
System.out.println(" or 'Why not do Y instead?'");
System.out.println("\n2. You answer with a decision: 'I chose to [do X], because'");
System.out.println(" [state the problem it solves].");
System.out.println("\n3. You explain what it anticipates:");
System.out.println(" 'This matters when [curveball arrives]. The new requirement");
System.out.println(" lands cleanly because [seam already exists].'");
System.out.println("\n4. You name the cost of not choosing it:");
System.out.println(" 'Without this decision, I'd be [retrofitting / rewriting /");
System.out.println(" editing the most fragile class]. That costs [time / risk / lines].'");
System.out.println("\n" + "=".repeat(70));
System.out.println("How to Practise");
System.out.println("=".repeat(70));
System.out.println("\nRead a reference solution.");
System.out.println("Find one architectural choice: an interface, a data structure,");
System.out.println(" a separation of concerns.");
System.out.println("Ask yourself: 'Why this way, not the obvious alternative?'");
System.out.println("Write the four parts of the defence.");
System.out.println("Then check if the reference's own comments back up your answer.");
System.out.println("\n" + "=".repeat(70));
System.out.println("When It Matters Most");
System.out.println("=".repeat(70));
System.out.println("\nThe entries that carry the most weight defend a REFUSAL.");
System.out.println("- Why NO concurrency in parking lot?");
System.out.println("- Why NO persistence?");
System.out.println("- Why NO metrics in rate-limiter?");
System.out.println("\nA decision to do something is easier to defend than a");
System.out.println("decision NOT to. The 'why not' is what separates a candidate");
System.out.println("who thought about trade-offs from one who coded the happy path.");
System.out.println("\n" + "=".repeat(70));
System.out.println("The Interview Test");
System.out.println("=".repeat(70));
System.out.println("\nThe interviewer adds a curveball.");
System.out.println("\nScenario A (you anticipated it):");
System.out.println(" Curveball: 'Add metrics to the rate limiter.'");
System.out.println(" Your answer: 'Metrics are out of scope. But I put each rule");
System.out.println(" behind an interface, so this is a new decorator on");
System.out.println(" RateLimitAlgorithm. One new file, zero edits.'");
System.out.println(" Verdict: You're prepared. That's extensibility.");
System.out.println("\nScenario B (you didn't anticipate it):");
System.out.println(" Curveball: 'Add metrics.'");
System.out.println(" Your answer: (silence, or) 'Let me add a counter to the");
System.out.println(" decision logic...' (now editing RateLimiter, three files,");
System.out.println(" touching the lock, risking the invariant)");
System.out.println(" Verdict: You're retrofitting. That's fragile.");
System.out.println("\nBoth candidates can add the feature. The difference is");
System.out.println("whether they anticipated the cost in their design.");
}
}
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.