Top 28 SOLID Principles Interview Questions & Answers (2026)

What changed in 2026 drives
Mass-recruiter offer letters are flatter for 2026 batch - the 4-5 LPA ASE band has barely budged in three years while inflation eats real wages. Premium tracks (Digital, Pro, Elite, Specialist) are still where the differential lives, and they are entirely test-driven. If you are aiming higher than the default offer, the coding round is not optional pageantry - it is the entire interview.
What I'd actually study for this
- 01Two solid coding-round answers (1 medium-hard DSA each, with edge-case discussion) > five half-baked ones
- 02One real project you can defend end-to-end - file paths, design decisions, and what you would change
- 03One DBMS schema you actually built (not a textbook ER diagram), with at least 3 join-heavy queries written from memory
- 04Three behavioural STAR stories: failure recovered, conflict handled, ownership taken
Where most candidates trip up
The single biggest mistake is treating company-specific guides as primary prep and DSA as secondary. It is the opposite. Mass recruiters use the test as a filter, but premium tracks at every IT services company use coding to allocate offer band. Spend 70% of prep time on DSA + system fundamentals, 20% on company-specific patterns, 10% on HR rehearsal. Reverse that ratio and you collect the default offer.
Editorial commentary by Aditya Sharma · written for PapersAdda · not generated, not aggregated.
SOLID principles are asked in software engineering interviews at product companies, mid-size tech firms, and service companies alike. Candidates report that SOLID questions appear in system design rounds and OOP design sessions at Amazon, Microsoft, Flipkart, and TCS. Based on public preparation resources and candidate-reported interview accounts, questions on recognizing violations, refactoring code, and applying principles to real design scenarios appear most frequently.
Single Responsibility Principle (SRP)
Q1. What is the Single Responsibility Principle? Give an example of a violation and a fix. (Easy)
Definition: A class should have only one reason to change. It should have only one job.
Violation:
class Employee {
private String name;
private double salary;
// Responsibility 1: Business logic
public double calculateBonus() {
return salary * 0.10;
}
// Responsibility 2: Persistence
public void saveToDatabase() {
// SQL INSERT logic here
System.out.println("Saving " + name + " to DB");
}
// Responsibility 3: Formatting/reporting
public String generatePaySlip() {
return "PaySlip for: " + name + " Salary: " + salary;
}
}
This class has three reasons to change: business logic changes, database schema changes, and report format changes.
Fixed (SRP applied):
class Employee {
private String name;
private double salary;
public double calculateBonus() { return salary * 0.10; }
// Only business logic
}
class EmployeeRepository {
public void save(Employee e) {
// DB logic only
}
}
class PaySlipGenerator {
public String generate(Employee e) {
return "PaySlip for: " + e.getName();
}
}
Each class has exactly one reason to change.
Q2. Does SRP mean a class can only have one method? (Easy)
No. SRP does not limit the number of methods. It limits the number of responsibilities (reasons to change).
A class can have many methods as long as they all serve the same cohesive purpose. For example, a UserAuthentication class can have login(), logout(), resetPassword(), and validateToken() -- all related to authentication, all one responsibility.
The key question is: "If requirement X changes, does this class need to change? If requirement Y (unrelated to X) changes, does this class also need to change?" If yes to both, it has multiple responsibilities.
Cohesion: SRP pushes toward high cohesion -- methods within a class are closely related. Low cohesion (unrelated methods grouped together) violates SRP.
Q3. Is SRP only about classes, or does it apply elsewhere? (Medium)
SRP applies at multiple levels of abstraction:
- Function/method level: A function should do one thing. A 200-line function doing validation + business logic + persistence + logging violates function-level SRP.
- Class level: The most common discussion.
- Module/package level: A module should own one area of the system. A "utils" package with authentication helpers, DB wrappers, and PDF generation has poor module-level SRP.
- Microservice level: Each microservice should own one business domain (user service, order service, payment service). "God services" violate SRP at the service level.
Robert Martin's definition uses "reasons to change" tied to different organizational roles. The database admin, the business analyst, and the UI designer represent different actors. If the same class changes for all three, it violates SRP.
Open/Closed Principle (OCP)
Q4. What is the Open/Closed Principle? Show a violation and how to fix it with polymorphism. (Easy)
Definition (Bertrand Meyer / Robert Martin): Software entities should be open for extension but closed for modification. New behavior should be added by writing new code, not by changing existing tested code.
Violation:
class DiscountCalculator {
public double calculate(String customerType, double price) {
if (customerType.equals("REGULAR")) {
return price * 0.05;
} else if (customerType.equals("PREMIUM")) {
return price * 0.10;
} else if (customerType.equals("VIP")) {
return price * 0.20;
}
return 0;
}
}
Adding a new customer type requires modifying the existing class (and its tests), violating OCP.
Fixed (OCP with polymorphism):
interface DiscountStrategy {
double calculate(double price);
}
class RegularDiscount implements DiscountStrategy {
public double calculate(double price) { return price * 0.05; }
}
class PremiumDiscount implements DiscountStrategy {
public double calculate(double price) { return price * 0.10; }
}
class VIPDiscount implements DiscountStrategy {
public double calculate(double price) { return price * 0.20; }
}
class DiscountCalculator {
public double calculate(DiscountStrategy strategy, double price) {
return strategy.calculate(price);
}
}
Adding a new customer type (e.g., CorporateDiscount) requires adding a new class, not modifying existing ones.
Q5. How does OCP relate to the Strategy and Decorator patterns? (Medium)
Both patterns are OCP implementations:
Strategy pattern: Defines a family of algorithms, encapsulates each, and makes them interchangeable. The context class is closed for modification (doesn't change when you add strategies) but open for extension (add new strategy classes).
The discount example above is a Strategy pattern. The context (DiscountCalculator) depends on the interface (DiscountStrategy), not concrete implementations.
Decorator pattern: Wraps an object to add behavior at runtime without modifying the original class.
interface TextProcessor {
String process(String text);
}
class PlainText implements TextProcessor {
public String process(String text) { return text; }
}
class UpperCaseDecorator implements TextProcessor {
private TextProcessor wrapped;
public UpperCaseDecorator(TextProcessor t) { this.wrapped = t; }
public String process(String text) { return wrapped.process(text).toUpperCase(); }
}
class TrimDecorator implements TextProcessor {
private TextProcessor wrapped;
public TrimDecorator(TextProcessor t) { this.wrapped = t; }
public String process(String text) { return wrapped.process(text).trim(); }
}
// Usage: compose at runtime
TextProcessor processor = new UpperCaseDecorator(new TrimDecorator(new PlainText()));
PlainText is never modified, but behavior is extended via decorators.
Q6. Does OCP mean you should never modify existing code? (Medium)
No. OCP is a design goal, not an absolute rule.
When modification is appropriate:
- Fixing bugs in existing code: you must modify the buggy code
- Modifying code during initial development (before it is "closed")
- When the abstraction itself was wrong: sometimes the right answer is to redesign the abstraction rather than contort extensions
The closed part refers to: code that is already deployed, tested, and working as part of a stable API. Changing it risks breaking existing behavior. Extensions via new classes have no impact on existing callers.
Pragmatic interpretation: OCP discourages modifying stable interfaces and behavior. If every new feature requires modifying core classes, the system is hard to evolve. The goal is to anticipate the right extension points.
Liskov Substitution Principle (LSP)
Q7. What is the Liskov Substitution Principle? Give a classic violation example. (Easy)
Definition (Barbara Liskov, 1987): If S is a subtype of T, then objects of type T may be replaced with objects of type S without altering any of the desirable properties of the program.
In simpler terms: subclasses must be behaviorally compatible with their parent class. Code using the base class must work correctly with any subclass.
Classic violation -- Square extends Rectangle:
class Rectangle {
protected int width, height;
public void setWidth(int w) { this.width = w; }
public void setHeight(int h) { this.height = h; }
public int area() { return width * height; }
}
class Square extends Rectangle {
@Override
public void setWidth(int w) {
this.width = w;
this.height = w; // Force equal sides
}
@Override
public void setHeight(int h) {
this.height = h;
this.width = h; // Force equal sides
}
}
The LSP violation:
void expandWidth(Rectangle r) {
r.setWidth(10);
r.setHeight(5);
assert r.area() == 50; // Passes for Rectangle, FAILS for Square
// Square overrides setHeight to also set width to 5,
// so area = 25, not 50.
}
Code written for Rectangle breaks when a Square is substituted, violating LSP.
Fix: Square and Rectangle should not share an inheritance relationship. Use a common interface Shape with area() instead.
Q8. How do preconditions and postconditions relate to LSP? (Hard)
LSP has formal constraints related to behavioral contracts:
Preconditions: Conditions that must be true before a method executes (inputs).
- LSP: subclass preconditions must be equal to or weaker than base class preconditions.
- A subclass cannot demand more from its caller than the base class does. If it does, code that satisfied base class preconditions may fail with the subclass.
Postconditions: Conditions that must be true after a method executes (outputs).
- LSP: subclass postconditions must be equal to or stronger than base class postconditions.
- A subclass must do at least as much as the base class promised.
Example violation of postcondition:
class Bird {
public void fly() {
// Postcondition: bird is airborne
}
}
class Penguin extends Bird {
@Override
public void fly() {
throw new UnsupportedOperationException("Penguins cannot fly");
}
// Postcondition violated: caller expects bird to be airborne after fly()
}
Invariants: Properties that must remain true throughout an object's lifetime.
- LSP: subclasses must preserve base class invariants.
The IS-A relationship is behavioral, not just structural. A Penguin IS-A Bird biologically, but not behaviorally in the context of a flying Bird class.
Q9. How do you identify an LSP violation in a code review? (Medium)
Signs of LSP violation:
- Type checks in polymorphic code:
void process(Animal animal) {
if (animal instanceof Dog) {
((Dog) animal).bark();
} else if (animal instanceof Cat) {
((Cat) animal).meow();
}
}
If you need to downcast and check types, the polymorphism is broken.
-
Throwing exceptions for inherited methods: Subclass overrides a method and throws
UnsupportedOperationExceptionorNotImplementedException. Classic example: Java'sAbstractList.remove()throws by default. -
Empty overrides: Subclass overrides a method but does nothing. Behavior is lost rather than extended.
-
Weakened return types: Subclass method returns null where base class guaranteed a non-null value.
-
Override that silently changes semantics: The Square/Rectangle example: override looks valid but breaks assumptions in code that uses the base class.
Fix pattern: When LSP is violated, reconsider the inheritance hierarchy. Often composition or extracting a more specific interface is the right answer.
Interface Segregation Principle (ISP)
Q10. What is the Interface Segregation Principle? Show a violation. (Easy)
Definition (Robert Martin): No client should be forced to depend on methods it does not use. Large interfaces should be split into smaller, more specific ones.
Violation -- "fat interface":
interface Worker {
void work();
void eat();
void sleep();
}
class HumanWorker implements Worker {
public void work() { System.out.println("Working"); }
public void eat() { System.out.println("Eating"); }
public void sleep() { System.out.println("Sleeping"); }
}
class RobotWorker implements Worker {
public void work() { System.out.println("Processing"); }
public void eat() { throw new UnsupportedOperationException(); } // Robots don't eat
public void sleep() { throw new UnsupportedOperationException(); } // Robots don't sleep
}
RobotWorker is forced to implement eat() and sleep() even though it does not need them.
Fixed (ISP applied):
interface Workable {
void work();
}
interface Eatable {
void eat();
}
interface Sleepable {
void sleep();
}
class HumanWorker implements Workable, Eatable, Sleepable {
public void work() { System.out.println("Working"); }
public void eat() { System.out.println("Eating"); }
public void sleep() { System.out.println("Sleeping"); }
}
class RobotWorker implements Workable {
public void work() { System.out.println("Processing"); }
// No forced implementation of eat() or sleep()
}
Q11. How does ISP improve testability? (Medium)
ISP improves testability in two ways:
1. Smaller mocking surface: When a class depends on a fat interface with 10 methods but uses only 2, tests must provide mock implementations of all 10. With ISP, the class depends on a focused interface with only 2 methods. The mock is simpler and less brittle.
// Fat interface: mock must implement all methods
class MockWorker implements Worker {
public void work() {} // needed
public void eat() {} // not needed, but must implement
public void sleep() {} // not needed, but must implement
}
// ISP: mock only what's needed
class MockWorkable implements Workable {
public void work() {}
}
2. Reduced coupling for test setup: A class depending on a narrow interface does not care about changes to unrelated methods in other parts of the fat interface. Test setup is more stable.
3. Role-based testing:
ISP naturally encourages testing objects in their specific roles. You test the Workable role separately from the Eatable role.
Q12. When should you split an interface under ISP vs keeping it together? (Medium)
Split interfaces when:
- Implementing classes must throw
UnsupportedOperationExceptionfor methods - Two groups of methods are always used together by some clients but never together by others
- The interface changes frequently in one part but rarely in another (different axes of change)
- You have many small classes that only need a subset of methods
Keep interfaces together when:
- All methods are always used together by all clients (high cohesion)
- The interface represents a genuine unified role with all methods necessary for that role
- Splitting would create so many interfaces that the code becomes hard to navigate (over-engineering)
Rule of thumb: If you find yourself writing "N/A" or throwing exceptions for methods in implementations, that is a signal to split. If the interface models a genuine role and all clients use all methods, leave it together.
Real example: Java's List vs Collection vs Iterable:
Java correctly segregates: Iterable (just iterate), Collection (basic ops), List (indexed access). Code that only needs iteration depends on Iterable, not List. This is ISP applied in the JDK.
Dependency Inversion Principle (DIP)
Q13. What is the Dependency Inversion Principle? What is "inversion"? (Easy)
DIP has two parts:
- High-level modules should not depend on low-level modules. Both should depend on abstractions.
- Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions.
Without DIP (high depends on low directly):
class OrderService {
private MySQLDatabase db = new MySQLDatabase(); // direct dependency on concrete
public void saveOrder(Order order) {
db.save(order);
}
}
OrderService (high-level) directly instantiates MySQLDatabase (low-level). Changing to PostgreSQL requires modifying OrderService.
With DIP (both depend on abstraction):
interface Database {
void save(Order order);
}
class MySQLDatabase implements Database {
public void save(Order order) { /* MySQL logic */ }
}
class PostgreSQLDatabase implements Database {
public void save(Order order) { /* PostgreSQL logic */ }
}
class OrderService {
private Database db; // depends on abstraction
public OrderService(Database db) {
this.db = db; // injected, not instantiated here
}
public void saveOrder(Order order) {
db.save(order);
}
}
The "inversion": In traditional layered architecture, high-level code calls low-level code (high depends on low). DIP inverts this: both depend on an abstraction. The dependency arrow flips -- concrete details implement the abstraction defined by the high-level policy.
Q14. What is Dependency Injection? How does it relate to DIP? (Medium)
Dependency Injection (DI) is a technique for achieving DIP. Instead of a class creating its own dependencies, the dependencies are provided (injected) from outside.
DI is the mechanism; DIP is the principle. You can have DI without DIP (injecting concrete classes), but following DIP naturally leads to DI.
Three forms of dependency injection:
Constructor injection (preferred):
class OrderService {
private final Database db;
public OrderService(Database db) { this.db = db; } // injected via constructor
}
Makes dependency explicit, supports immutability, easy to test.
Setter injection:
class OrderService {
private Database db;
public void setDatabase(Database db) { this.db = db; }
}
Optional dependencies, allows reconfiguration after construction.
Field injection (avoid in new code):
class OrderService {
@Autowired // Spring annotation
private Database db; // injected by framework via reflection
}
Convenient but hides dependencies, harder to test without framework.
DI containers (frameworks): Spring (Java), Guice (Java), Dagger (Android), and others automate dependency graphs:
@Service
class OrderService {
private final Database db;
@Autowired
public OrderService(Database db) { this.db = db; }
}
Spring manages the lifecycle of Database and injects it into OrderService.
Q15. Is DIP the same as Dependency Injection? (Easy)
No. They are related but distinct:
DIP (principle): A design rule about the direction of dependencies. High-level code should depend on abstractions, not concretions. Tells you WHAT to depend on.
Dependency Injection (pattern/technique): A mechanism for providing dependencies to a class from outside, rather than having the class create them. Tells you HOW to provide dependencies.
Inversion of Control (IoC): A broader principle where control of object creation and lifecycle is delegated to an external container or framework. DI is the most common way to implement IoC.
Example showing difference:
// DIP followed (depends on abstraction) but NO DI (creates concrete internally):
class OrderService {
private Database db = DatabaseFactory.getInstance(); // factory, not injection
// This follows DIP if DatabaseFactory returns the interface type,
// but does not use DI.
}
// DIP + DI together:
class OrderService {
private Database db;
public OrderService(Database db) { this.db = db; } // DI
// Depends on Database interface (DIP) AND injected from outside (DI)
}
Applying SOLID Together
Q16. How are SOLID principles related to each other? (Medium)
The principles reinforce each other:
SRP enables OCP: When a class has one responsibility, it is easier to extend without breaking other functionality. If a class does many things, adding a new feature likely requires modifying multiple responsibilities.
OCP enables LSP: If you build extensions via subclasses/implementations, LSP guides you to build them correctly (behavioral compatibility). Violations of LSP often become apparent when you try to use OCP-style polymorphism.
ISP enables DIP: Smaller, focused interfaces are better candidates for abstractions that high-level modules depend on. A fat interface as the abstraction for DIP still has the ISP coupling problem.
DIP unifies everything: If high-level modules depend on good abstractions, and those abstractions are well-segregated (ISP) and have clear single responsibilities (SRP), the system becomes highly modular and extensible (OCP) with correct substitutability (LSP).
Common anti-pattern that violates all five:
class GodClass {
public void createUser() { /* user creation */ }
public void sendEmail() { /* email logic */ }
public void saveToMysql() { /* database logic */ }
public void generatePdf() { /* PDF generation */ }
public void logActivity() { /* logging */ }
}
Violates: SRP (5 responsibilities), OCP (adding feature needs modification), likely LSP if extended, ISP (clients forced to see all methods), DIP (nothing depends on abstractions).
Q17. Refactor this code to apply all relevant SOLID principles. (Hard)
Problem code:
class ReportManager {
public void generateReport(String type) {
if (type.equals("PDF")) {
// connect to DB
// get data
// format as PDF
// send via email
} else if (type.equals("HTML")) {
// connect to DB
// get data
// format as HTML
// send via email
}
}
}
Step 1: Identify violations.
- SRP: generates, formats, and sends - three responsibilities
- OCP: adding a new format (CSV) requires modifying
generateReport() - DIP: likely creating concrete DB and email objects internally
Step 2: Refactored design.
// Abstractions (DIP)
interface DataRepository {
List<Record> fetchReportData();
}
interface ReportFormatter {
String format(List<Record> data);
}
interface ReportSender {
void send(String content, String recipient);
}
// Concrete implementations (extend without modifying)
class MySQLRepository implements DataRepository { /* ... */ }
class PDFFormatter implements ReportFormatter {
public String format(List<Record> data) { return "PDF content"; }
}
class HTMLFormatter implements ReportFormatter {
public String format(List<Record> data) { return "<html>...</html>"; }
}
// OCP: add CSVFormatter without modifying anything above
class CSVFormatter implements ReportFormatter {
public String format(List<Record> data) { return "csv,content"; }
}
class EmailSender implements ReportSender {
public void send(String content, String recipient) { /* send via SMTP */ }
}
// Single responsibility: orchestration only
class ReportService {
private DataRepository repo;
private ReportFormatter formatter;
private ReportSender sender;
// DIP via constructor injection
public ReportService(DataRepository repo, ReportFormatter formatter, ReportSender sender) {
this.repo = repo;
this.formatter = formatter;
this.sender = sender;
}
public void generateAndSend(String recipient) {
List<Record> data = repo.fetchReportData();
String content = formatter.format(data);
sender.send(content, recipient);
}
}
Result: Each class has one reason to change (SRP). New formats are added without modification (OCP). ReportService depends on interfaces (DIP). Interfaces are focused (ISP). Subclasses are fully substitutable (LSP).
Q18. How do SOLID principles apply in a microservices architecture? (Hard)
SOLID principles scale from class-level to service-level design:
SRP at service level: Each microservice owns one business domain. A "user-service" handles only user management. Mixing order processing into user-service violates service-level SRP. If user schema changes AND order logic changes require touching the same service, that is a service-level SRP violation.
OCP at service level: Service contracts (APIs) should be versioned and backward-compatible. Adding a new endpoint does not break existing callers. Changing an existing endpoint breaks callers (modification vs extension). Use API versioning (/v1/users, /v2/users) to extend without breaking.
LSP at service level: If Service B is a replacement or alternative for Service A (e.g., payment processors), callers of A should work with B without changes. API contracts must be honored.
ISP at service level: Service APIs should expose only what clients need. A monolithic service exposing 50 endpoints where each client uses 3 of them is a service-level ISP violation. Consumer-Driven Contract testing (Pact) formalizes what each consumer actually needs.
DIP at service level: Services should depend on abstract contracts (API specs, event schemas) not on specific implementations. Service A should not embed knowledge of how Service B's database is structured. Message schemas in event-driven architectures are the abstraction; concrete consumers depend on them.
Q19. What is the God Object anti-pattern and which SOLID principles does it violate? (Medium)
A God Object (or God Class) is a class that knows too much or does too much. It centralizes too many responsibilities and has too many dependencies.
Typical symptoms:
- Thousands of lines in one class
- Instance variables used only by some methods (not cohesive)
- Methods that span multiple domains (user logic + payment logic + notifications)
- All other classes depend on it
- Hard to unit test (must mock many dependencies)
SOLID violations:
| Principle | Violation |
|---|---|
| SRP | Multiple unrelated responsibilities in one class |
| OCP | Every new feature modifies the class; no extension points |
| LSP | Hard to extend correctly; subclasses inherit massive state |
| ISP | Clients forced to depend on the full God class interface |
| DIP | God class often creates all its dependencies internally |
Real-world emergence: God classes grow gradually. A "Manager" or "Service" class that starts small becomes a dumping ground over months. Code reviews and regular refactoring prevent this.
Refactoring approach: Extract cohesive groups of methods + their data into separate classes. Use the Move Method and Extract Class refactoring patterns.
Code Examples and Design Questions
Q20. Write a Python example demonstrating all five SOLID principles. (Hard)
from abc import ABC, abstractmethod
from typing import List
# ISP: separate interfaces for separate roles
class Readable(ABC):
@abstractmethod
def read(self, item_id: int) -> dict:
pass
class Writable(ABC):
@abstractmethod
def write(self, data: dict) -> None:
pass
# DIP: abstractions for notification and storage
class NotificationSender(ABC):
@abstractmethod
def send(self, message: str, recipient: str) -> None:
pass
class UserStorage(Readable, Writable):
pass # Combines both roles
# Concrete implementations depend on abstractions (DIP)
class DatabaseUserStorage(UserStorage):
def read(self, user_id: int) -> dict:
return {"id": user_id, "name": "Alice"} # simulated
def write(self, data: dict) -> None:
print(f"Saving user {data} to DB")
# OCP: new sender added without modifying existing code
class EmailSender(NotificationSender):
def send(self, message: str, recipient: str) -> None:
print(f"Email to {recipient}: {message}")
class SMSSender(NotificationSender):
def send(self, message: str, recipient: str) -> None:
print(f"SMS to {recipient}: {message}")
# LSP: both senders can be substituted for NotificationSender
def notify_user(sender: NotificationSender, msg: str, recipient: str):
sender.send(msg, recipient) # works with any NotificationSender subclass
# SRP: UserService only orchestrates, does not contain storage or notification logic
class UserService:
def __init__(self, storage: UserStorage, notifier: NotificationSender):
# DIP: depends on abstractions injected via constructor
self.storage = storage
self.notifier = notifier
def register(self, user_data: dict) -> None:
self.storage.write(user_data) # SRP: delegate storage
self.notifier.send( # SRP: delegate notification
f"Welcome {user_data['name']}",
user_data["email"]
)
# Usage: compose dependencies at the top level
service = UserService(
storage=DatabaseUserStorage(),
notifier=EmailSender()
)
service.register({"name": "Bob", "email": "[email protected]"})
# Swap EmailSender for SMSSender without changing UserService (OCP + DIP)
What each principle achieves here:
- SRP:
UserServiceonly orchestrates;DatabaseUserStorageonly stores;EmailSenderonly sends - OCP: new senders or storage backends added without modifying
UserService - LSP:
EmailSenderandSMSSenderfully substitutable forNotificationSender - ISP:
ReadableandWritableare separate interfaces - DIP:
UserServicedepends onUserStorageandNotificationSenderabstractions
Q21. What is the Hollywood Principle and how does it relate to DIP? (Medium)
The Hollywood Principle: "Don't call us, we'll call you." High-level components define the framework and call into low-level components, not the reverse.
Relation to DIP:
- Without DIP: low-level code is called directly by high-level code (bottom-up calls)
- With DIP: high-level code defines abstractions; low-level code implements them and is called by frameworks/high-level code
- This is exactly "don't call us, we'll call you" -- the framework calls your implementation, your implementation does not call the framework
Template Method pattern as Hollywood Principle:
abstract class DataProcessor {
// Template method: high-level defines the algorithm
public final void process() {
fetchData(); // defined here (high-level)
processData(); // abstract: subclass fills in
saveData(); // defined here
}
abstract void processData(); // Low-level fills in the blank
void fetchData() { /* default */ }
void saveData() { /* default */ }
}
class CSVProcessor extends DataProcessor {
void processData() { System.out.println("Processing CSV"); }
// "Don't call us (the framework), we'll call you (via template method)"
}
The framework (DataProcessor) calls the subclass (CSVProcessor), not the other way around.
Q22. How would you explain SOLID to a junior developer who only knows basic OOP? (Easy)
Use a restaurant analogy:
SRP - One cook, one job: Don't have one chef cook, serve, wash dishes, and manage accounts. Separate roles: one person per responsibility. When the dishwasher breaks, you fix the dishwasher, not the chef.
OCP - Add a menu item, don't rewrite the kitchen: When you add a new dish, you add a recipe card. You don't rebuild the stove. The kitchen (core code) stays stable; the menu (behavior) extends.
LSP - Any waiter can take your order: If the system expects "a waiter," any specific waiter (junior, senior, part-time) must be able to fulfill the role correctly. A waiter who ignores orders is not a waiter.
ISP - Give the kitchen a small notepad, not a novel: The chef only needs the order slip, not the customer's full history. Don't force someone to receive information they don't use.
DIP - The restaurant doesn't care which supplier delivers the tomatoes: The restaurant depends on "tomato supplier" (abstraction), not "Sharma Farms specifically" (concrete). Switching suppliers does not change restaurant operations.
Q23. What is "design smell" and which SOLID violations create which smells? (Medium)
A design smell is a surface indication of a deeper design problem. Common smells:
Rigidity (hard to change): Violation of OCP and DIP. Every change ripples through many classes. Caused by depending on concretions rather than abstractions, and not having extension points.
Fragility (breaks in unrelated places): Violation of SRP. A change in one area breaks unrelated functionality because too many concerns are coupled in one class.
Immobility (hard to reuse): Violation of DIP and ISP. You want to reuse a class but it drags too many dependencies with it. The class depends on specific implementations rather than interfaces.
Viscosity (hard to do the right thing): Violation of OCP. When adding a feature, the path of least resistance is to hack it in (modify existing code) rather than extend properly because the extension points do not exist.
Needless complexity: Over-engineering toward OCP without clear need. Not every if-statement needs a Strategy pattern. Apply SOLID when the smell is present, not preemptively everywhere.
Q24. How do SOLID principles impact unit testing? (Medium)
SOLID makes code easier to unit test:
SRP - Smaller tests: Classes with one responsibility have fewer methods and clearer behavior. Tests for one responsibility don't interfere with tests for another.
OCP - Tests survive refactoring: When you add behavior via extension (new classes) rather than modifying existing classes, existing tests for the unchanged code remain valid. You only write new tests for new classes.
LSP - Polymorphic test helpers: You can write a test helper that works with the base type and reuse it for any subtype. If LSP is violated, you need different test logic per subtype.
// Works for any correct implementation of Database
void testSaveAndRetrieve(Database db) {
db.save(new Order(1, "test"));
assert db.findById(1) != null;
}
ISP - Minimal mocks: Classes depending on narrow interfaces need minimal mocking surface. Tests are simpler and less brittle to unrelated interface changes.
DIP - Injectable test doubles: Constructor injection makes it trivial to substitute real dependencies with mocks, stubs, or fakes in tests.
// Test with a fake in-memory database instead of real MySQL
OrderService service = new OrderService(new InMemoryDatabase(), new FakeEmailSender());
Without DIP, testing OrderService would require a real database connection.
Q25. What is the difference between SOLID principles and design patterns? (Easy)
SOLID principles are design guidelines -- abstract rules about how to structure code for maintainability, extensibility, and flexibility. They tell you WHAT properties your design should have.
Design patterns are reusable solutions to commonly occurring design problems. They tell you HOW to solve specific structural or behavioral problems.
Relationship:
-
Many design patterns are implementations of SOLID principles:
- Strategy pattern implements OCP and DIP
- Decorator pattern implements OCP
- Factory/Abstract Factory implements DIP
- Observer pattern implements SRP (separates state change from notification) and OCP
-
SOLID is more fundamental: even without formal design patterns, following SOLID produces good designs
-
Design patterns are more concrete: they specify class structure, relationships, and roles precisely
Analogy: SOLID principles are like "use ergonomic furniture" (general principle). Design patterns are like "a stand-up desk" or "a lumbar-support chair" (specific solutions to specific ergonomic problems).
Q26. What is cohesion and coupling? How do SOLID principles address them? (Medium)
Cohesion: The degree to which elements within a module belong together. High cohesion = elements strongly related and focused. Low cohesion = unrelated elements grouped together.
Coupling: The degree of interdependence between modules. Tight coupling = changes in one module force changes in others. Loose coupling = modules can change independently.
Goal: High cohesion + Loose coupling.
How SOLID addresses coupling and cohesion:
| Principle | Addresses | How |
|---|---|---|
| SRP | Both | One responsibility = high cohesion within class, natural boundaries between classes |
| OCP | Coupling | Extensions via new classes instead of modifying dependents |
| LSP | Coupling | Correct substitutability means callers don't need to know the concrete type |
| ISP | Coupling | Narrow interfaces reduce the surface of coupling |
| DIP | Coupling | Depending on abstractions insulates high-level modules from low-level changes |
Measuring coupling:
- Afferent coupling (Ca): number of classes that depend on this class (stability indicator)
- Efferent coupling (Ce): number of classes this class depends on (instability indicator)
- Instability = Ce / (Ca + Ce). Stable packages (low instability) should be abstract (DIP).
Q27. Give a real-world example of an OCP violation in production code and how to fix it. (Hard)
Real-world scenario: Payment processing
// Version 1: handles two payment methods
class PaymentProcessor {
public void process(Order order, String method) {
if (method.equals("CREDIT_CARD")) {
// Call credit card gateway
CreditCardGateway gateway = new CreditCardGateway();
gateway.charge(order.total, order.cardNumber);
} else if (method.equals("PAYPAL")) {
// Call PayPal API
PayPalAPI paypal = new PayPalAPI();
paypal.requestPayment(order.total, order.paypalEmail);
}
// When UPI is added in v2: modify this class + add else if
// When Crypto is added in v3: modify again
// Every modification risks breaking credit card and PayPal paths
}
}
Production consequence: In e-commerce, payment code has the highest test coverage and review scrutiny. Every modification requires regression testing of ALL payment methods. A bug in the new UPI block can break credit card processing if tests miss an edge case.
Fix:
interface PaymentGateway {
void process(Order order);
}
class CreditCardGateway implements PaymentGateway {
public void process(Order order) {
// Only credit card logic
}
}
class PayPalGateway implements PaymentGateway {
public void process(Order order) {
// Only PayPal logic
}
}
// Adding UPI: write new class, zero risk to existing
class UPIGateway implements PaymentGateway {
public void process(Order order) {
// Only UPI logic
}
}
class PaymentProcessor {
public void process(Order order, PaymentGateway gateway) {
gateway.process(order); // Never changes
}
}
Factory to select gateway (closed to modification):
class GatewayFactory {
public static PaymentGateway create(String method) {
return switch (method) {
case "CREDIT_CARD" -> new CreditCardGateway();
case "PAYPAL" -> new PayPalGateway();
case "UPI" -> new UPIGateway();
default -> throw new IllegalArgumentException("Unknown method: " + method);
};
}
}
Only GatewayFactory is modified when a new method is added. PaymentProcessor and existing gateways are never touched.
Q28. Which SOLID principle is most commonly violated in codebases and why? (Medium)
SRP is most commonly violated, followed closely by DIP.
Why SRP is most violated:
- Natural tendency: "while I'm in this class, let me add this feature here"
- No single change feels drastic; violations accumulate gradually
- No linting rule prevents it (unlike syntax errors)
- Time pressure: extracting a proper class takes more time than adding a method
- Difficulty: "one reason to change" requires architectural judgment, not just technical skill
Signs SRP is the most common issue:
- "Service" and "Manager" classes in most Java codebases accumulate 5+ responsibilities
- "Utils" files in Python/JS projects become catch-all files
- Large controllers in MVC frameworks mixing validation, business logic, and persistence
Why DIP follows closely:
- Convenient to instantiate concrete classes directly (
new MySQLDatabase()) - DI frameworks (Spring, Guice) require setup overhead
- Junior developers often don't think about testability until tests become painful
- Legacy code predates DI frameworks; retrofitting requires significant refactoring
Least commonly violated: ISP tends to be better maintained because interface bloat is immediately visible when implementing classes have to write stub methods.
Frequently Asked Questions
Q: Do I need to follow all SOLID principles all the time? SOLID principles are guidelines, not laws. Apply them when complexity justifies the abstraction. A 20-line script processing one file does not need dependency injection. A payment system with multiple gateways and growing business rules does. Apply SOLID proportionally to the system's complexity and expected change rate.
Q: Can following SOLID principles lead to over-engineering? Yes. Creating a separate interface and implementation for every tiny class produces indirection without value. The cure: apply SOLID when there is a concrete reason (existing violation, anticipated change, testability need), not preemptively everywhere. Keep the code as simple as possible, and refactor toward SOLID as complexity grows.
Q: What is the relationship between SOLID and Clean Architecture? Robert Martin's Clean Architecture book applies SOLID at the architectural level. Dependency Rule (outer layers depend inward, never inward on outer) is DIP at architecture scale. Use cases as the core of business logic is SRP at the system level. Clean Architecture is SOLID expressed for entire systems, not just classes.
Q: Which SOLID principle is hardest to understand for beginners? DIP causes the most confusion because "inversion" is counterintuitive. Beginners expect high-level code to depend on lower-level implementations (that is the natural direction). Understanding that the ABSTRACTION should be owned/defined by the high-level module (not by the low-level implementation) is the key insight.
Internal Links
Methodology applied to this articlelast verified 8 Jun 2026
- No fabricated salary numbers or success rates. If we quote a range, it's sourced.
- No noun-substituted templates. This article was not generated by swapping company names in a stock prompt.
- No paid placements, sponsored coaching links, or affiliate-shilled course pushes.
Explore this topic cluster
More resources in Interview Questions
Use the category hub to browse similar questions, exam patterns, salary guides, and preparation resources related to this topic.
Paid contributor programme
Sat this this year? Share your story, earn ₹500.
First-person experience reports help future candidates prep smarter. We pay verified contributors ₹500 via UPI per accepted story - with byline.
Submit your story →Ready to practice?
Take a free timed mock test
Put what you learned into practice. Our mock tests match the 2026 pattern with timer, navigator, reveal, and score breakdown. No signup.
Start Free Mock Test →Related Articles
Airbnb Interview Questions 2026: Top Tech, HR & Behavioural Q&As for Freshers
Clearing Airbnb's fresher loop in 2026 comes down to preparing for the exact mix of questions across technical, behavioural,...
Airtel Interview Questions 2026: Top Tech, HR & Behavioural Q&As for Freshers
Clearing Airtel's fresher loop in 2026 comes down to preparing for the exact mix of questions across technical, behavioural,...
AMD Interview Questions 2026: Top Tech, HR & Behavioural Q&As for Freshers
Clearing AMD's fresher loop in 2026 comes down to preparing for the exact mix of questions across technical, behavioural,...
Atlassian Interview Questions 2026: Top Tech, HR & Behavioural Q&As for Freshers
Clearing Atlassian's fresher loop in 2026 comes down to preparing for the exact mix of questions across technical,...
Barclays Interview Questions 2026
_Last verified by [Aditya Sharma](/author/aditya-sharma/) · cross-checked against PapersAdda Hiring Pulse and...
More from PapersAdda
Amazon Leadership Principles Interview 2026: All 16 + STAR
Accenture Interview Questions 2026 (with Answers for Freshers)
Capgemini Interview Questions 2026 (with Answers for Freshers)
HCLTech Interview Questions 2026 (TechBee + TGT, with Answers)