If you remember five things
The whole answer in one glance. Everything below is the defense of these five calls.
- DRY is about knowledge, not characters. One home per business rule. Two blocks that merely look alike but change for different reasons are not a violation, and merging them is the mistake.
- KISS is scored by the reader. Simple means a tired teammate can follow it at midnight, not fewest lines. A clever regex one-liner fails KISS even when it is shorter.
- YAGNI beats speculative generality. Build the requirement in front of you. The imagined future arrives in a shape you did not predict, so the hooks you pre-installed are wrong anyway.
- Low coupling, high cohesion is the dial. Modules talk through small stable interfaces, and each class does one job. This is the one pair you can actually draw, so it earns the points.
- Narrate the trade-off out loud. "Gateway stays behind an interface, no cache yet (YAGNI), here is where it would go." Saying it on purpose is what separates senior from mid-level.
1The problem it solves#
Code does not rot because it was wrong on day one. It rots three specific ways, and these principles are the names we give those failures so we can catch them early.
The three ways a codebase dies.
- Duplication drifts. The same logic lives in four places and they fall out of sync.
- Everything touches everything. One change ripples across files that had no business being involved.
- Speculative machinery. Someone built a plugin framework for a feature that still has one implementation.
I have watched all three kill real systems.
- A pricing rule copied into the mobile client, the web client, and a nightly batch job. The tax law changed, one copy got missed, and the invoices went out wrong. That is a DRY failure.
- A class that knows the database, the HTTP layer, and the email templates all at once. You cannot test any of it without standing up the whole world, so nobody tests it. That is low cohesion plus high coupling.
- An abstraction layer with three interfaces and a factory feeding one concrete class that never got a sibling in two years. That is a YAGNI failure.
What each principle fights.
- DRY fights duplication of knowledge.
- KISS fights needless cleverness.
- YAGNI fights speculative generality.
- Coupling and cohesion are the two dials that tell you whether your module boundaries sit in the right places.
Get these right and the fancy patterns mostly take care of themselves. Strategy, Factory, and Observer are just these principles with sharper names.
Nobody asks you to define cohesion in an interview. They hand you a feature twist and grade whether your design absorbs it with one new class or forces a rewrite. These five words are the levers behind that single moment.
2The idea (mental model)#
Here is how I hold each one in my head. Short, so I can say it at the whiteboard without a speech.
DRY (Don't Repeat Yourself).
- Every piece of knowledge has one home. The key word is knowledge, not characters.
- Two functions that look similar but encode different business rules are not a DRY violation.
- A tax calculation living in three files is: change the rate and you have three edits, one of which will be missed.
KISS (Keep It Simple).
- Prefer the solution a tired teammate can read at midnight during an incident.
- Simple is not short, and it is not easy to write.
- A clever one-liner with a regex and a nested ternary is easy to write and hard to read. That fails KISS even at five lines shorter.
YAGNI (You Aren't Gonna Need It).
- Build for the requirement in front of you, not the one you imagine.
- The future requirement either never comes, or comes in a shape you did not predict.
- Either way the hooks you pre-installed are wrong, and now you maintain two mistakes.
Coupling.
- How much one module depends on the guts of another.
- Low coupling means I can change module A without opening module B. That is the goal.
Cohesion.
- How well the things inside one module belong together.
- High cohesion means a class does one job and everything in it serves that job.
The gut-check I use at the whiteboard. Aim for low coupling, high cohesion, and watch for two tells.
- A class name with "And" or "Manager" or "Util" in it usually has low cohesion.
- If changing one feature forces edits across five classes, coupling is probably high.
- Neither check is a law, but both have saved me from bad designs more than once.
Low coupling, high cohesion is the only design dial you can draw. So draw it.
3Structure#
Coupling and cohesion are the two you can put on a whiteboard, and drawing them is what earns points. Picture an order checkout.
The low-cohesion version: one OrderProcessor does everything.
- Validates the cart, charges the card, writes the database, sends the email.
- Four reasons to change, one class. That is low cohesion.
- It reaches into the payment gateway's concrete type and the SMTP client directly, so a change in either ripples straight into the core. That is high coupling.
The clean version: split by responsibility.
OrderServiceorchestrates, nothing more.- It talks to a
PaymentGatewayinterface, anOrderRepositoryinterface, and aNotifierinterface. - Each collaborator has one job (high cohesion), and
OrderServicedepends on abstractions, not concrete classes (low coupling). - Swapping Stripe for Razorpay means writing one new class, not editing the orchestrator or touching a single line inside
checkout.
OrderService points only at the three interfaces, never the concrete classes, and the concrete classes implement those interfaces so they are swappable by construction. The filled diamond on Order *-- OrderItem is composition: items have no life outside their order, so kill the order and the items die with it. This is the same shape as the dependency inversion idea in SOLID.4Implementation in Java#
First, what bad looks like. One class, everything inside, welded to concrete types.
// Low cohesion, high coupling. Avoid this.
class OrderProcessor {
void process(Cart cart) {
if (cart.getItems().isEmpty()) throw new IllegalStateException("empty");
double total = 0;
for (Item i : cart.getItems()) total += i.price() * i.qty();
StripeClient stripe = new StripeClient("sk_live_..."); // hard dependency
stripe.charge(total);
Connection c = DriverManager.getConnection("jdbc:..."); // hard dependency
// ... raw SQL insert ...
SmtpSender smtp = new SmtpSender("smtp.myhost.com"); // hard dependency
smtp.send(cart.getUserEmail(), "Thanks for your order");
}
}
What is wrong, specifically.
- You cannot test
processwithout a live Stripe key, a database, and an SMTP server. - Four responsibilities, three concrete dependencies baked in with
new. - The total is computed inline with a
double: a money bug waiting to happen, and a DRY violation the moment a second caller needs the same number.
Reveal the answer
new calls are what make process untestable, and testability is the first thing an interviewer probes. Pull the dependencies behind interfaces passed through the constructor and you can run the whole flow with fakes and zero network. Cohesion (splitting the four jobs) is the follow-on move, but the hard dependencies are the bleeding wound. Fix coupling first.Now the version that respects the principles.
interface PaymentGateway { PaymentResult charge(long cents); }
interface OrderRepository { void save(Order order); }
interface Notifier { void send(Order order); }
class OrderService {
private final PaymentGateway gateway;
private final OrderRepository repo;
private final Notifier notifier;
OrderService(PaymentGateway gateway, OrderRepository repo, Notifier notifier) {
this.gateway = gateway;
this.repo = repo;
this.notifier = notifier;
}
Order checkout(Cart cart) {
cart.validate(); // cart owns its own rules (DRY)
long cents = cart.totalInCents(); // total computed in ONE place
PaymentResult result = gateway.charge(cents);
if (!result.success()) {
throw new PaymentFailedException(result.reason());
}
Order order = Order.from(cart, result.transactionId());
repo.save(order);
notifier.send(order);
return order;
}
}
Three things to point at while the interviewer reads it.
- DRY. The total lives in
Cart.totalInCents()as integer cents, so every caller gets the same number and nobody fights floating-point rounding. - Low coupling.
OrderServicedepends on interfaces passed through the constructor, so I hand it fakes in a unit test and run the whole checkout with zero network calls. - YAGNI. No plugin registry, no strategy map, no YAML config for payment providers I do not have yet. When a second gateway shows up, I write one class that implements
PaymentGatewayand change one line at the composition root.
On KISS. checkout reads top to bottom like a sentence: validate, charge, build, save, notify. Nobody needs a diagram to follow it, and that readability is the point, not the line count.
5A real-world example#
The clearest place I see these play out is a notification system, a common LLD prompt at companies like Uber and Atlassian.
The early version: a NotificationManager with an if/else chain.
- If the channel is email do this, if SMS do that, if push do the other.
- Low cohesion: one class knows every channel's quirks.
- High coupling: adding Slack means editing the one class everyone else also edits, a merge-conflict magnet.
- Quiet DRY violation: retry and backoff logic gets copy-pasted into each branch and drifts. The email branch retries three times, the SMS branch retries five, and nobody meant for that.
The clean version: each channel behind a Channel interface.
- Cohesion goes up: each class knows exactly one delivery mechanism and nothing else.
- The shared retry and logging logic lives in one
ReliableChannelwrapper (the Decorator pattern). That is the DRY win: the retry policy now has one home.
The YAGNI part that trips people up.
- Do not build a rules engine that lets admins configure channel routing through a UI unless the prompt asks for it.
- Name the extension point out loud instead: "I would add a routing strategy here if we needed per-user preferences," then move on.
- Stating the seam shows judgment. Building it shows you did not hear the requirement.
Why this pattern is worth memorizing. That same split, an orchestrator plus one-job collaborators behind interfaces, is the backbone of a parking lot design, a rate limiter, a logging framework, and a vending machine. Learn it once and you have a template for most of the catalog.
6When to use it, and when not to#
Every principle has an edge where it stops helping. Knowing the edge is the senior part.
DRY. Apply it to knowledge, not to lines that happen to match.
- The trap is deduplicating two things that look identical today but represent different concepts.
- Force them to share code and a change to one breaks the other, and you spend a sprint untangling them.
- If two chunks have different reasons to change, let them stay separate. A little duplication is far cheaper than the wrong abstraction.
KISS. Almost always on.
- The rare exception is where the simple version genuinely cannot meet a hard requirement, like a hot path that needs a cache or a lock-free structure to hit a latency target.
- Even then, isolate the complexity behind a clean interface so the rest of the code stays simple and the clever part is one small, well-tested box.
YAGNI. Default to it in interviews and in most product code.
- Pull back when the cost of adding something later is brutal: a public API contract, or a database schema that millions of rows will depend on.
- There, a little upfront thought is cheap insurance, because migrating a live schema under load is one of the worst jobs in the business.
- For internal code you control end to end, wait.
Coupling and cohesion. Always the goal, but do not chase the extremes.
- Do not chase zero coupling. Modules have to talk to do anything useful. The aim is that they talk through small, stable interfaces instead of reaching into each other's internals.
- Do not split a cohesive class just to hit a class-count target. One well-named class that does one job beats three anemic ones that only make sense read together.
7Common mistakes#
Five failure modes I see on repeat, each a principle applied without judgment.
- Over-DRYing into a god helper. A
Utilsclass with forty unrelated static methods is not DRY, it is a dumping ground with terrible cohesion. DRY means one home per piece of knowledge, not one home for all knowledge. - Confusing simple with easy. People defend a dense clever block by calling it "simpler" because it is fewer lines. KISS is about the reader. Measure it by how fast a new teammate understands it cold, not by how good it felt to write.
- YAGNI as an excuse for sloppiness. YAGNI says do not build unused features. It does not say skip error handling or write the thing badly. Simple and unfinished are different, and conflating them is how you ship bugs with a clear conscience.
- Faking low coupling with an interface that has one implementation forever. An interface helps only if it hides something that varies or needs swapping in tests. Wrapping every class in an interface out of habit adds indirection and buys nothing. That is YAGNI biting back, now with extra files to navigate.
- Chasing cohesion metrics instead of responsibilities. The question is never "how many methods," it is "does everything here serve one job." Split by responsibility, not by line count, and let the method count fall out of that.
Reaching for a generic plugin system, a config-driven rules engine, or an interface on every class before anyone asked for a second implementation. It reads as "I cannot tell signal from habit." Name the seam, do not build it, and the interviewer sees judgment instead of gold-plating.
8Where it shows up in interviews#
Interviewers rarely ask you to explain DRY. They watch for it while you design something else. Here are the signals they grade silently.
- You compute once and reuse. Put the total in one place instead of recomputing it in three methods. That is DRY, and the recompute is the single most common tell of a junior answer.
- You absorb the twist with one class. When they say "now support a second payment provider," a low-coupling design means you add a class and change one line. A high-coupling one means you rewrite the core. This is the most common follow-up I see, a coupling test disguised as a feature request.
- You resist gold-plating. Start sketching a generic plugin system before anyone asked and a sharp interviewer marks a YAGNI miss. Name the extension point, do not build it.
- You split on reasons to change. "I pulled payment out because it changes for different reasons than persistence" is a cohesion answer, and it lands because it shows you split on reasons, not on vibes.
I am not waiting for definitions. I am watching whether you narrate the trade-off as you draw: "gateway stays behind an interface so the service stays decoupled; no cache yet, YAGNI, but here is where it goes." That running commentary is the clearest senior signal I get, because it proves you are choosing on purpose rather than by accident.
"Two methods look almost identical. Should I DRY them up?"
"Now add a second payment provider. How much of this changes?"
PaymentGateway, and one line at the composition root where I wire it in. The orchestrator does not change, and nothing inside checkout moves. That is the whole point of depending on the interface instead of the concrete client."When is wrapping a class in an interface the wrong call?"
"How do you know a class has low cohesion?"
"You said YAGNI, but schemas are expensive to migrate. Isn't that a contradiction?"
What you can now explain in the room
- Why DRY is about knowledge, not matching lines, and when deduplicating is actively the wrong move.
- Why KISS is scored by the reader at midnight, not by line count.
- When YAGNI flips, and why schemas and public APIs are the exception.
- How to draw low coupling and high cohesion with an orchestrator plus interfaces.
- Why pulling payment, persistence, and notification apart makes the design absorb a new provider in one class.
- How to narrate the trade-offs out loud so the interviewer hears judgment instead of habit.
Next, pair this with the SOLID principles and the Strategy and Factory patterns, which are really just these ideas with sharper names.
Get a prep plan built around your Design Principles: DRY, KISS, YAGNI, Coupling and Cohesion interview
Tell us the role and your interview date. We will send back a dated, day by day plan. Free.