Loading content...
In LLD interviews, interviewers assume you know object-oriented syntax. They're not checking whether you can write a class or define an interface. What they're evaluating is something deeper: whether you truly understand why OOP concepts exist and when to apply them.\n\nThis distinction is critical. Many candidates can recite the four pillars—encapsulation, inheritance, polymorphism, and abstraction—but struggle to apply them wisely. They inherit when they should compose. They expose internals when they should encapsulate. They force polymorphism where simple conditionals suffice. These missteps reveal superficial understanding.
By the end of this page, you will understand how interviewers probe for genuine OOP comprehension, what signals distinguish surface knowledge from deep understanding, and how to demonstrate mastery through your design choices and explanations.
Object-oriented understanding in an LLD context goes far beyond knowing syntax. Interviewers evaluate whether you can:\n\n1. Model domains accurately using classes, interfaces, and relationships\n2. Apply encapsulation to protect invariants and hide complexity\n3. Use inheritance and composition appropriately based on relationship semantics\n4. Leverage polymorphism to enable extensibility where it matters\n5. Create meaningful abstractions that simplify without obscuring\n6. Avoid OOP anti-patterns that emerge from misunderstanding\n\nThe evaluation happens organically as you design. Every class you define, every method you assign, every relationship you draw reveals your understanding level.
Interviewers rarely ask 'What is polymorphism?' directly. Instead, they watch whether your design naturally uses polymorphism where appropriate—and critically, whether you can explain why you made that choice when asked. The test is implicit in your design, not explicit in a quiz.
Many developers think encapsulation means making fields private and adding getters/setters. This misses the point entirely. True encapsulation is about protecting invariants and hiding implementation decisions.\n\nWhat invariants means:\n\nAn invariant is a condition that must always be true for an object to be in a valid state. For example:\n- A Booking should always have a valid showId and at least one seat\n- A PaymentTransaction should never have a negative amount\n- A ParkingSpot that is occupied should always reference the occupying vehicle\n\nEncapsulation ensures these invariants cannot be violated from outside the object.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647
// WEAK ENCAPSULATION - Invariants can be violated externallypublic class BookingBad { private List<Seat> seats = new ArrayList<>(); private BookingStatus status; // Problem: External code can add null seats or empty the list public List<Seat> getSeats() { return seats; } // Problem: Status can be set to anything public void setStatus(BookingStatus status) { this.status = status; }} // STRONG ENCAPSULATION - Object protects its own invariantspublic class BookingGood { private final List<Seat> seats; private BookingStatus status; public BookingGood(Show show, List<Seat> selectedSeats) { if (selectedSeats == null || selectedSeats.isEmpty()) { throw new IllegalArgumentException("Booking requires at least one seat"); } // Defensive copy to prevent external modification this.seats = new ArrayList<>(selectedSeats); this.status = BookingStatus.PENDING; } // Return immutable view - can't be modified public List<Seat> getSeats() { return Collections.unmodifiableList(seats); } // Object controls valid transitions public void confirm(PaymentConfirmation payment) { if (status != BookingStatus.PENDING) { throw new IllegalStateException("Can only confirm pending bookings"); } // ... validate payment this.status = BookingStatus.CONFIRMED; } public void cancel(String reason) { if (status == BookingStatus.COMPLETED) { throw new IllegalStateException("Cannot cancel completed bookings"); } this.status = BookingStatus.CANCELLED; }}When you discuss your design, mention invariants explicitly: 'This class enforces that a booking always has at least one seat—that's an invariant it protects.' This signals deep understanding of encapsulation's purpose, not just mechanical application.
Perhaps no OOP decision reveals understanding more clearly than the choice between inheritance and composition. Interviewers watch this closely because it's frequently misapplied.\n\nThe Core Distinction:\n\n- Inheritance models an "is-a" relationship: A Dog is an Animal\n- Composition models a "has-a" relationship: A Car has an Engine\n\nBut this simplistic framing is often insufficient. The deeper question is: Should behavior be shared through a class hierarchy or through delegation?
| Factor | Favors Inheritance | Favors Composition |
|---|---|---|
| Relationship Type | True 'is-a' that won't change (Circle is-a Shape) | 'Has-a' or 'can-do' relationships (Car has-a Engine) |
| Behavior Variation | Subclasses are genuine specializations | Behavior varies independently of the object's type |
| Substitutability | Subclasses fully substitute for parent (Liskov) | Behaviors can be swapped without changing the object |
| Coupling Tolerance | Acceptable tight coupling; hierarchy is stable | Need loose coupling; behaviors may change independently |
| Multiple Behaviors | Single line of variation | Multiple orthogonal axes of variation |
The Interview Trap: Over-Inheritance\n\nCandidates frequently overuse inheritance because it seems natural. Common mistakes include:\n\n- PremiumUser extends User (Role behavior should be composed, not inherited—users can change roles)\n- EmailNotification extends Notification when Notification should compose a notifier strategy\n- Creating deep hierarchies where multiple levels share little behavior\n\nInterviewers look for whether you instinctively reach for inheritance or whether you evaluate both options.
Inheritance creates tight coupling between parent and child classes. Changes to the parent can break children in subtle ways. Interviewers are wary of deep hierarchies because they've seen this problem in production. Prefer shallow hierarchies (usually just one level) and use composition for most behavior sharing.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
// PROBLEMATIC INHERITANCE - Price calculation varies independentlyabstract class Vehicle { protected String licensePlate; protected VehicleType type; abstract double calculateParkingPrice(Duration duration);} class Car extends Vehicle { double calculateParkingPrice(Duration duration) { /* car pricing */ }} class ElectricCar extends Car { // Deep inheritance to add charging! private ChargingMeter meter; @Override double calculateParkingPrice(Duration duration) { return super.calculateParkingPrice(duration) + meter.getChargeCost(); }}// What if we need ElectricMotorcycle? Multiple inheritance problem! // BETTER: COMPOSITION - Pricing is a separate concernclass Vehicle { private String licensePlate; private VehicleType type; private PricingStrategy pricingStrategy; Vehicle(String licensePlate, VehicleType type, PricingStrategy pricing) { this.licensePlate = licensePlate; this.type = type; this.pricingStrategy = pricing; } double calculateParkingPrice(Duration duration) { return pricingStrategy.calculate(this, duration); }} interface PricingStrategy { double calculate(Vehicle vehicle, Duration duration);} class StandardPricing implements PricingStrategy { ... }class ElectricPricing implements PricingStrategy { // Adds charging cost private StandardPricing basePricing; double calculate(Vehicle v, Duration d) { return basePricing.calculate(v, d) + getChargingCost(v, d); }}// Now ANY vehicle can have electric pricing without deep inheritance!Polymorphism—the ability to treat different implementations through a common interface—is powerful but often misapplied. Interviewers assess whether you use polymorphism to solve real variability or whether you force it unnecessarily.\n\nWhen polymorphism earns its place:\n\n1. Multiple implementations exist or are expected — Different payment processors, notification channels, scheduling algorithms\n2. The calling code shouldn't know the specific type — Processing payments without knowing if it's Stripe, PayPal, or bank transfer\n3. New implementations will be added — The system must be open for extension\n4. Complex conditionals would otherwise emerge — Replace type-checking with polymorphic dispatch
Demonstrating Polymorphism Understanding in Interviews:\n\nWhen you introduce an interface or abstraction, explain why:\n\n*"I'm defining a PaymentProcessor interface because the system needs to support multiple payment methods—Stripe today, but likely PayPal and bank transfers soon. The booking service should work with any processor without knowing which one."*\n\nThis connects polymorphism to a concrete need, not abstract 'best practice.'
One of polymorphism's strongest use cases is replacing type-checking conditionals. If you find yourself writing 'if type == A then ... else if type == B then ...', this is often a signal that polymorphism should dispatch behavior instead. Interviewers respect candidates who recognize this pattern.
Abstraction is about hiding complexity behind simpler interfaces. But the art lies in finding the right level—abstracting enough to simplify without obscuring essential details.\n\nToo Little Abstraction:\n- Every detail is exposed\n- Clients must understand internal workings\n- Changes ripple across the entire codebase\n- Cognitive load is high\n\nToo Much Abstraction:\n- Simple operations require navigating layers\n- The actual behavior is hidden behind indirection\n- Debugging becomes archaeological excavation\n- Performance may suffer from abstraction overhead\n\nJust Right:\n- Clients interact with meaningful concepts\n- Implementation details are hidden but accessible when needed\n- Changes are localized appropriately\n- The abstraction maps to how people think about the domain
| Scenario | Too Little | Too Much | Just Right |
|---|---|---|---|
| Parking lot spot allocation | Client iterates arrays, checks spot sizes, handles concurrency | AbstractSpotAllocatorFactory → AllocatorBuilder → Strategy → Policy → Executor | ParkingLot.findAvailableSpot(VehicleType) — returns spot or null |
| Payment processing | Client constructs HTTP requests to payment APIs, parses responses, handles retries | PaymentFacade → PaymentOrchestrator → PaymentAdapter → PaymentClient → PaymentSerializer | PaymentProcessor.charge(amount, method) — returns PaymentResult |
| Notification sending | Client manages SMTP connections, SMS API keys, push notification tokens | NotificationEngine → ChannelRouter → ChannelFactory → ChannelImplManager → Sender | NotificationService.notify(user, message, channel) — handles channel-specific logic internally |
Interviewers' Abstraction Test:\n\nInterviewers often probe abstraction decisions:\n- "Why did you create an interface here?"\n- "What if I only need one implementation?"\n- "This seems like a lot of layers—can we simplify?"\n\nStrong candidates defend their abstractions with concrete reasons or acknowledge when they've over-abstracted and simplify.
A useful heuristic: Don't abstract until you've seen three instances. One case isn't a pattern. Two may be coincidence. Three suggests genuine variation worth abstracting. This prevents premature abstraction while still capturing proven patterns.
A key OOP skill is assigning responsibilities to the right objects. This connects to the Single Responsibility Principle, but more fundamentally it's about information expert—the object with the data should have the behavior.\n\nThe Interview Test:\n\nInterviewers watch where you place methods. If Booking holds the seats, then seat-related calculations belong there, not in a separate service. If ParkingLot tracks capacity, then availability checks belong there.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152
// POOR: Service knows too much about Booking's internalsclass BookingService { boolean isCancellable(Booking booking) { // Service reaches into Booking's state - wrong! List<Seat> seats = booking.getSeats(); BookingStatus status = booking.getStatus(); LocalDateTime showTime = booking.getShow().getStartTime(); return status != BookingStatus.CANCELLED && status != BookingStatus.COMPLETED && showTime.isAfter(LocalDateTime.now().plusHours(1)); } void cancel(Booking booking) { if (isCancellable(booking)) { booking.setStatus(BookingStatus.CANCELLED); // Setter! // Service handles all the logic externally } }} // BETTER: Booking knows its own rulesclass Booking { private BookingStatus status; private Show show; private List<Seat> seats; public boolean isCancellable() { // Booking has the data, so it should answer the question return status != BookingStatus.CANCELLED && status != BookingStatus.COMPLETED && show.getStartTime().isAfter(LocalDateTime.now().plusHours(1)); } public void cancel(String reason) { if (!isCancellable()) { throw new IllegalStateException("Booking cannot be cancelled at this time"); } this.status = BookingStatus.CANCELLED; // Booking manages its own state }} class BookingService { // Service orchestrates, doesn't micromanage void processCancel(BookingId id, String reason) { Booking booking = repository.find(id); booking.cancel(reason); // Delegate to the expert repository.save(booking); notifyUser(booking); }}If your domain objects are mostly data containers (getters/setters only) with all logic in services, you have an anemic domain model. This isn't always wrong—in some architectures it's intentional—but in LLD interviews, interviewers often prefer rich domain models where objects have behavior. Be prepared to discuss the trade-offs.
Interviewers watch for anti-patterns that reveal gaps in OOP understanding. Avoiding these mistakes signals experience:
| Anti-Pattern | What It Looks Like | Why It's Problematic | Better Approach |
|---|---|---|---|
| God Object | One class (Manager, Controller, Handler) that does everything | Violates SRP, becomes maintenance nightmare, impossible to test | Decompose into focused classes with single responsibilities |
| Primitive Obsession | Using strings, ints everywhere instead of domain types | No type safety, can pass wrong values, no behavior encapsulation | Create value objects: Money, Email, VehicleId, etc. |
| Feature Envy | A method uses more of another object's data than its own | Behavior is in the wrong place; breaks information expert | Move the method to the object whose data it uses |
| Yo-Yo Problem | Deep inheritance hierarchy requiring constant jumping between classes | Hard to understand, fragile, defeats cognitive benefits of OOP | Prefer shallow hierarchies and composition |
| Refused Bequest | Subclass inherits but doesn't use or overrides parent methods to do nothing | Violates Liskov Substitution, indicates wrong hierarchy | Question if inheritance is appropriate; use composition instead |
Classes named 'XxxManager', 'XxxHelper', or 'XxxUtil' often indicate poor responsibility assignment. They tend to become God Objects. When you're tempted to create one, ask: 'Which existing object should own this behavior?' or 'What focused object should I create instead?'
Your OOP understanding comes through in every design choice. Here's how to make it visible:
Beyond specific techniques, what impresses interviewers is that you THINK about OOP deliberately. You consider trade-offs, question defaults, and make intentional choices. Even if you make a suboptimal choice, explaining your reasoning shows the understanding they're looking for.
We've explored the second critical dimension interviewers evaluate: deep comprehension of object-oriented principles and their appropriate application. Let's consolidate:
What's next:\n\nWith design thinking and OOP understanding covered, the next page explores pattern knowledge application—how interviewers assess whether you truly understand design patterns and can apply them purposefully rather than mechanically.
You now understand how interviewers evaluate OOP comprehension. This goes far beyond syntax—it's about applying encapsulation, inheritance, polymorphism, and abstraction wisely, and assigning responsibilities to the right objects. Next, we'll examine how to demonstrate pattern knowledge effectively.