SOLID principles are the five foundational guidelines for writing maintainable object-oriented code. In LLD interviews at Amazon, Flipkart, Swiggy, and Google, interviewers expect you to not just know the acronym but to apply each principle to real system design decisions. This guide explains all five principles with concrete LLD examples and how interviewers test them.
What Are SOLID Principles?
SOLID is an acronym coined by Robert C. Martin (Uncle Bob):
- S — Single Responsibility Principle (SRP)
- O — Open/Closed Principle (OCP)
- L — Liskov Substitution Principle (LSP)
- I — Interface Segregation Principle (ISP)
- D — Dependency Inversion Principle (DIP)
These principles guide class design decisions. Violating them makes code fragile, hard to test, and resistant to change. In an LLD interview, an interviewer who asks "why did you separate these two classes?" is testing whether you know SRP.
S — Single Responsibility Principle
A class should have only one reason to change. "Reason to change" means one business actor whose requirements can force the class to be modified.
LLD Example: Parking Lot
// VIOLATION: ParkingLot does pricing, persistence, and slot management
public class ParkingLot {
public Ticket parkVehicle(Vehicle v) { ... }
public double calculateFee(Ticket t) { ... } // pricing responsibility
public void saveToDatabase(Ticket t) { ... } // persistence responsibility
}
// CORRECT: Separate responsibilities
public class ParkingLot {
public Ticket parkVehicle(Vehicle v) { ... } // slot management only
}
public class PricingService {
public double calculateFee(Ticket t) { ... }
}
public class TicketRepository {
public void save(Ticket t) { ... }
}If the pricing model changes, only PricingService changes. If the database schema changes, only TicketRepository changes. ParkingLot is untouched by either.
O — Open/Closed Principle
Software entities should be open for extension but closed for modification. Add new behavior by adding new code, not by changing existing code.
LLD Example: Notification System
// VIOLATION: Every new channel requires modifying NotificationService
public class NotificationService {
public void send(String type, String message) {
if (type.equals("EMAIL")) { emailGateway.send(message); }
else if (type.equals("SMS")) { smsGateway.send(message); }
// Adding WhatsApp requires editing this class
}
}
// CORRECT: Closed for modification, open for extension via Strategy
public interface NotificationChannel {
void send(String message);
}
public class EmailChannel implements NotificationChannel { ... }
public class SMSChannel implements NotificationChannel { ... }
public class WhatsAppChannel implements NotificationChannel { ... } // new — no existing code changed
public class NotificationService {
private final Map<String, NotificationChannel> channels;
public void send(String type, String message) {
channels.get(type).send(message); // unchanged when adding new channel
}
}L — Liskov Substitution Principle
Subtypes must be substitutable for their base types without altering the correctness of the program. If you have code that works with a Vehicle, it must work with any subclass of Vehicle.
LLD Example: Payment Methods
// VIOLATION: CryptocurrencyPayment extends CardPayment but throws on getCvv()
public class CryptocurrencyPayment extends CardPayment {
@Override
public String getCvv() {
throw new UnsupportedOperationException(); // breaks LSP
}
}
// CORRECT: Model the correct hierarchy
public abstract class Payment {
public abstract PaymentResult process(double amount);
}
public class CardPayment extends Payment {
private String cvv;
@Override
public PaymentResult process(double amount) { ... }
}
public class CryptocurrencyPayment extends Payment {
private String walletAddress;
@Override
public PaymentResult process(double amount) { ... }
}LSP violations are often caught by tests — if you need to check instanceof before calling a method, the hierarchy is wrong.
I — Interface Segregation Principle
Clients should not be forced to depend on interfaces they do not use. Split fat interfaces into smaller, role-specific ones.
LLD Example: Ride Sharing
// VIOLATION: Driver forced to implement unrelated methods
public interface RideSharingActor {
void requestRide(Location from, Location to); // for passengers only
void acceptRide(String rideId); // for drivers only
void cancelRide(String rideId);
void viewEarnings(); // for drivers only
}
// CORRECT: Separate by role
public interface Passenger {
void requestRide(Location from, Location to);
void cancelRide(String rideId);
}
public interface Driver {
void acceptRide(String rideId);
void viewEarnings();
void cancelRide(String rideId);
}
// Driver implements Driver, Passenger implements Passenger
// No class is forced to implement irrelevant methodsD — Dependency Inversion Principle
High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details; details should depend on abstractions.
LLD Example: Inventory Management
// VIOLATION: InventoryService depends directly on MySQL implementation
public class InventoryService {
private final MySqlInventoryRepository repo = new MySqlInventoryRepository(); // concrete dependency
public InventoryItem findById(String id) {
return repo.findById(id); // cannot swap to MongoDB without changing InventoryService
}
}
// CORRECT: Depend on abstraction; inject concrete implementation
public interface InventoryRepository {
InventoryItem findById(String id);
void save(InventoryItem item);
}
public class InventoryService {
private final InventoryRepository repo; // depends on abstraction
public InventoryService(InventoryRepository repo) { // injected — testable
this.repo = repo;
}
}
// Production: new InventoryService(new MySqlInventoryRepository())
// Test: new InventoryService(new InMemoryInventoryRepository())How Interviewers Test SOLID Principles
- "Why did you create a separate PricingService?" — Tests SRP. Answer: pricing and parking lot management are two separate reasons to change.
- "What if I want to add a new split type to Splitwise?" — Tests OCP. Answer: add a new SplitStrategy class; no existing code changes.
- "Can I use a CryptoCurrency object wherever I use a Card?" — Tests LSP. Show the correct hierarchy where all payment types share a common interface.
- "How would you test the BookingService in isolation?" — Tests DIP. Answer: inject a mock repository via the constructor — possible only because BookingService depends on an interface, not a concrete class.
SOLID Quick Reference
Principle | One-Line Summary | Key Pattern --------- | ------------------------------------ | ------------------ SRP | One class, one reason to change | Separate services OCP | Extend behavior, not modify code | Strategy, Decorator LSP | Subclasses must honor base contracts | Correct inheritance ISP | Small, focused interfaces | Role interfaces DIP | Depend on abstractions, not concrets | Constructor injection
Common Follow-Up Questions
- "Is it always wrong for a class to have two responsibilities?" — SRP says one reason to change, not one method. A small helper class that both parses and validates a format may be fine if both change together for the same reason.
- "Does OCP mean I can never modify a class?" — No. Bug fixes and requirement changes that affect the class's own behavior are expected. OCP prevents adding new behavior by forking the existing class with a new if-else branch.
- "Can SOLID principles conflict?" — Yes. Strict ISP can create many tiny interfaces that complicate DIP injection. Strict DIP can make simple code verbose. Apply principles as guidelines, not rules — where the tradeoff makes sense for the complexity at hand.
FAQ — SOLID Principles in LLD Interviews
Which SOLID principle is most commonly tested in LLD interviews?
SRP and OCP are the most frequently tested. SRP because most candidates over-stuff classes with unrelated responsibilities. OCP because the Strategy pattern — the canonical OCP implementation — appears in almost every LLD problem (pricing, splitting, assignment).
How do you explain DIP to an interviewer?
High-level logic should not know which database, email provider, or payment gateway it is using. It should program to an interface (InventoryRepository, EmailSender). The concrete implementation is injected at startup. This makes high-level modules testable in isolation and swappable without changing business logic.
What is the difference between SRP and ISP?
SRP applies to classes: one class, one reason to change. ISP applies to interfaces: one interface, one client role. A class can implement multiple narrow interfaces (ISP) and still have a single responsibility (SRP). Both principles reduce coupling, but at different levels of abstraction.
How do SOLID principles relate to design patterns?
Design patterns are concrete implementations of SOLID principles. Strategy implements OCP. Decorator implements OCP and SRP. Factory implements DIP (callers depend on an abstract factory, not concrete constructors). Observer implements SRP (separates event source from event handlers). Knowing both layers — the principle and the pattern — is what interviewers expect.