If you remember five things
Carve the prompt into objects first, defend the carving second. Everything below is that carving.
- Nouns become classes, verbs become methods, relationships become fields. Read the prompt out loud and tag the words. This is the whole opening move, and most candidates skip it.
- Private data, public behavior. A class guards its own invariants, so a bad balance can only come from one place. Expose what the object does, never its raw state.
- Program to an interface, not an implementation. The service depends on a contract, so new behavior is a new class and zero edits to the control flow.
- Polymorphism is what absorbs new requirements. One loop, many behaviors. Adding a channel or a payment rail touches no existing code. This is the pillar interviewers watch for.
- Composition over inheritance. Inheritance couples the child to the parent hard. Reach for it only on a genuine is-a that passes Liskov. Everything else is a field.
1The problem it solves#
You get a prompt like "design an elevator system" and your brain floods: buttons, floors, doors, requests, scheduling. Where do you even start typing?
Procedural thinking makes this worse.
- Model everything as a pile of functions plus global data, and every new requirement touches ten places.
- Add a second elevator and your
handleRequest()now needs arrays, indexes, and which car is idle. - The code grows sideways and nothing is reusable.
- By the 30-minute mark you are editing the same function for the fourth time, and the interviewer has already decided.
OOP carves the mess into pieces that own their own data and behavior.
- An
Elevatorknows its own floor and direction. - A
Requestknows where it came from and where it wants to go. - A
Controllerdecides which elevator takes the request. - Each piece is small, each is testable in isolation, and a requirement change usually touches one class instead of ten.
In an interview, OOP is also how you communicate.
- When I ask you to design something, I am watching how you break the problem into nouns and responsibilities.
- That breakdown is the actual signal I grade, well before you write a single line of Java.
Nobody is grading whether you can recite "the four pillars." They are grading whether you can take an open prompt, name the right objects fast, and then add a requirement without rewriting half the board. The vocabulary is just how you show that thinking out loud.
2The idea (mental model)#
The whiteboard mental model is one line: nouns become classes, verbs become methods, and the relationships between nouns become fields.
- "A parking lot has many parking spots." Lot is a class, spot is a class, the lot holds a collection of spots.
- "A car parks in a spot." That is a method on the lot, or a relationship between car and spot.
- You are reading the problem statement out loud and tagging the words.
- It sounds too easy to be a real technique. It is the real technique, and most candidates skip it and start typing methods instead.
Then four ideas shape how you build those classes. These are the famous four pillars, but hold them as design moves, not trivia.
Encapsulation: keep data private, expose behavior.
- A
BankAccountshould never let you doaccount.balance = -500from outside. - You call
withdraw(amount)and the account enforces its own rules. - The class guards its own invariants, so a bug in balance can only come from one place.
Abstraction: expose the what, hide the how.
- A
PaymentProcessorhas apay(amount)method. - Callers do not care whether it talks to Stripe or Razorpay.
- The caller codes against a promise, not an implementation.
Inheritance: share behavior through a genuine is-a relationship.
- A
SavingsAccountis aBankAccount: reuse the common parts, add or change a few. - Use this sparingly. Section 6 is where I explain why.
Polymorphism: one interface, many behaviors.
- If every
Notificationhas asend()method, loop over a list and callsend()without caring whether each is email, SMS, or push. - This is the pillar that makes designs flexible, and the one interviewers love most, because it is what lets a design absorb new requirements.
If you remember nothing else: private data, public behavior, program to an interface.
That last phrase, "program to an interface, not an implementation," is straight out of the Gang of Four, and it is the single habit that unlocks most of OOP's payoff.
3Structure#
Let me make this concrete with a small notification system, because it shows all four pillars in one picture.
The shape, in plain words:
- We have an abstract idea: "a thing that can notify a user."
- Email, SMS, and push are specific kinds of that thing.
- A
NotificationServiceholds a list of channels and fans a message out to all of them. - It depends on the abstraction, not the concrete classes, so adding WhatsApp later means one new class and zero changes to the service. That zero is the whole point.
How the pieces relate:
- The service aggregates channels (they can live without it).
- Each concrete channel inherits from the abstract base.
- A
Messageis composed into the service as the thing being sent.
<|--) is inheritance (EmailNotifier is-a Notifier). The hollow diamond (o--) is aggregation (the service holds notifiers but does not own their lifecycle). The plain arrow (-->) is association (the service uses a Message when it sends).That diagram is roughly what I want on the board before you write a single line of Java. If you can draw this and talk through the three arrow types, you have already cleared the bar most candidates miss.
When a candidate draws the class box and then actually names each arrow ("this is aggregation because the channels outlive the service"), I relax. It tells me they have read UML in real code, not just memorized that a diamond exists. Say the relationship out loud, do not just draw the line.
4Implementation in Java#
Here is the design above in code. Notice how little there is. I am showing the shape, not every getter.
// Abstraction: the contract every channel obeys
interface Notifier {
boolean send(Message msg);
}
// A small value object. Encapsulated, immutable.
class Message {
private final String title;
private final String body;
Message(String title, String body) {
this.title = title;
this.body = body;
}
String getTitle() { return title; }
String getBody() { return body; }
}
// Polymorphism: each channel is a Notifier with its own behavior
class EmailNotifier implements Notifier {
private final String smtpHost;
EmailNotifier(String smtpHost) { this.smtpHost = smtpHost; }
public boolean send(Message msg) {
System.out.println("Email via " + smtpHost + ": " + msg.getTitle());
return true;
}
}
class SmsNotifier implements Notifier {
public boolean send(Message msg) {
System.out.println("SMS: " + msg.getBody());
return true;
}
}
// The service depends on the interface, never on concrete types
class NotificationService {
private final List<Notifier> channels = new ArrayList<>();
void addChannel(Notifier n) { channels.add(n); }
void notifyAll(Message msg) {
for (Notifier n : channels) {
n.send(msg); // one call, many behaviors
}
}
}
And the wiring:
NotificationService service = new NotificationService();
service.addChannel(new EmailNotifier("smtp.jetapply.com"));
service.addChannel(new SmsNotifier());
service.notifyAll(new Message("Welcome", "Your account is ready"));
Look at notifyAll. It has no idea what kinds of channels exist.
- Add push, add WhatsApp, add a Slack webhook: this loop never changes.
- That is the payoff of programming to an interface: the variation lives in the classes, not in the control flow.
- It is also the Open-Closed Principle in action: open for extension (new channels), closed for modification (the loop stays frozen).
Encapsulation is here too.
Messagefields are private and final.- Nobody can reach in and mutate the body after it is built.
- A message that left your code intact cannot be corrupted downstream.
Reveal the answer
notifyAll: swap the loop for one that returns on the first true. Zero channel classes change, zero new if branches per channel type. That is the tell of a design programmed to an interface: a new behavior lives in one method, not smeared across every concrete type.5A real-world example#
This exact shape is everywhere once you learn to spot it.
A payment flow in a real app:
- You have a
PaymentGatewayinterface and concrete implementations for cards, UPI, and wallets. - The checkout code calls
gateway.charge(order)and stays unaware of which rail runs underneath. - Add a new gateway and you drop in one class and ship.
I ran into this on a product that charges users in two regions.
- Indian cards go through one processor, international cards through another.
- The checkout logic should not sprout
if (region == "IN")branches, because that branch would need a twin in refunds, in receipts, in retries. - Instead a small factory picks the right
PaymentGatewayby charge currency, and the rest of the system just callscharge(). - That is the Factory pattern plus polymorphism doing real work: the one region decision lives in one place, and every caller downstream stays clean.
The same pattern powers half the systems you touch:
- Logging frameworks: one
Loggerinterface, with console, file, and network implementations. - The Java collections you use daily:
ListwithArrayListandLinkedListbehind it. - Streaming pipelines with swappable sources and sinks.
Once you see "interface plus swappable implementations," you see it everywhere, and naming it to the interviewer signals you have shipped code, not just read about it.
6When to use it, and when not to#
OOP is the default for LLD interviews, so the real question is which pillar to reach for, and when to hold back.
Reach for interfaces and polymorphism when you have a family of things that do the same job differently.
- Payment methods, notification channels, discount strategies (that is the Strategy pattern), file parsers.
- This is almost always the right move and it scores well.
Use composition over inheritance. This is the one to internalize.
- Inheritance looks tempting because it reuses code for free, but it couples the child to the parent hard, and the parent's changes leak into every subclass.
- If a
CarextendsEngine, you have said a car is-a engine, which is nonsense. A car has-an engine. Model it as a field, not a parent. - Save inheritance for genuine is-a relationships where the subtype can stand in for the base anywhere.
- That substitutability rule has a name, the Liskov Substitution Principle, and it is the honest test for whether inheritance is legal here.
Composition (has-a)
- Coupling
- Loose: the field is swappable at runtime.
- Change blast radius
- Small: the holder depends on an interface, not internals.
- Best when
- You want to reuse behavior or vary it later. Almost always.
- Breaks when
- Rarely. Worst case is a little extra wiring.
Inheritance (is-a)
- Coupling
- Tight: the child inherits all of the parent, wanted or not.
- Change blast radius
- Large: a parent change ripples into every subclass.
- Best when
- A true is-a that passes Liskov, two levels deep at most.
- Breaks when
- You reach for it just to reuse a method you have no is-a with.
When not to over-OOP.
- Modeling a plain bag of data with no behavior? Do not force five classes and a factory onto it. A simple record or struct is fine.
- Do not build deep inheritance trees for a 45-minute interview. Two levels is usually plenty.
- Interviewers notice when abstraction is earning its keep versus when you are decorating an empty design to look clever.
The honest rule: add a class or interface when it removes a conditional or isolates a thing that will change. Not before. Abstraction has a cost, and you pay it in reading time.
7Common mistakes#
Public fields everywhere.
- If your classes are just data with getters and setters for every field, you wrote a struct, not an object.
- Ask: does this class enforce any rule? A
BankAccountthat letsbalancego negative is broken encapsulation, and I will probe exactly that edge.
Inheritance for code reuse.
- The classic one: you want to reuse a method, so you extend a class you have no is-a with.
- Now you have dragged in behavior you did not want, and every parent change can break you.
- Prefer a field and delegation.
God classes.
- One
ManagerorSystemclass that parses, validates, stores, and notifies is procedural code wearing a class costume. - Split responsibilities so each class has one reason to change.
- That is the Single Responsibility Principle, and a 400-line
Manageris the fastest way to fail an LLD round.
Leaking internals.
- Returning a mutable reference to an internal list lets callers corrupt your state from the outside.
- Return a copy or an unmodifiable view.
- Small thing, and it separates people who have debugged this in production from people who have not.
Depending on concrete classes.
- If
NotificationServicehad typed its field asEmailNotifier, you would be stuck the moment you needed SMS. - Type against the interface.
- This single habit, the Dependency Inversion Principle, unlocks most of the flexibility OOP promises.
Reaching for extends because it saves you a few lines, then shrugging when I ask whether the subtype can stand in for the base. Inheritance without a real is-a is the single most common way a clean-looking design quietly rots. If you cannot pass the Liskov test out loud, make it a field instead.
8Where it shows up in interviews#
Every LLD prompt is an OOP exercise in disguise.
- Say "design a vending machine" and I expect the key classes named out loud (Machine, Product, Slot, Coin, State) within the first two minutes.
- That identification step is graded whether I say so or not.
Here is the flow I watch for during an interview:
if has shown me the design does not hold.That last exchange is the whole game.
- Throw "now support gift cards" at a payment design, and a strong candidate calmly adds one more
PaymentGatewayimplementation while the checkout loop stays untouched. - The candidate who instead hunts through old methods to splice in an
ifhas shown me the opposite.
Where you will meet it concretely:
- Parking lot, elevator, vending machine, library management, BookMyShow-style booking, rate limiter.
- Every one starts with the same move: read the nouns, draw the classes, pick your interfaces.
Practice this until it is reflex.
- Take any prompt, spend five minutes listing nouns and the verbs that connect them, sketch the class diagram before you code.
- The candidates who do this look senior. The ones who start typing methods look lost, and the gap is visible inside the first ten minutes.
"Why composition over inheritance? Give me the concrete cost of getting it wrong."
"Your NotificationService adds a channel at runtime. How does it stay closed to modification?"
"When would you NOT use OOP or add an interface here?"
"I return my internal list of channels to a caller. What breaks?"
"Your Manager class does parsing, validation, storage, and notification. Fix it."
What you can now explain in the room
- How to turn a vague prompt into classes: nouns to classes, verbs to methods, relationships to fields.
- What each of the four pillars actually buys you, framed as design moves rather than definitions.
- Why "program to an interface" lets a design absorb new requirements with one new class and zero edits.
- When inheritance is legal (a true is-a that passes Liskov) and why composition is the default.
- The five mistakes that fail an LLD round, and the SOLID principle each one violates.
- Why reading the three UML arrow types aloud signals you have read real code, not just slideware.
Next, pair this with the SOLID principles and the Strategy and Factory patterns. Those are the tools that turn a decent class diagram into a design an interviewer remembers.
Get a prep plan built around your Object-Oriented Programming for LLD interview
Tell us the role and your interview date. We will send back a dated, day by day plan. Free.