OOPs Design Principles SOLID 2026: Interview Guide with Examples

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.
Last Updated: June 2026
Why SOLID Matters in Placement Interviews
Candidates report SOLID principle questions in roughly 30-40% of SDE-2 and senior interviews at product companies, and in virtually all LLD rounds. Based on public preparation resources and candidate-reported interview threads from 2025 to 2026, interviewers at Flipkart, Amazon, and Razorpay explicitly ask "which SOLID principles does your design follow?" after you present an LLD solution.
The correct approach is not to recite definitions but to explain violations and how your design avoids them.
The Four Pillars of OOPs
| Pillar | Definition | Keyword |
|---|---|---|
| Encapsulation | Bundle data and behavior; hide internals | Private fields, public methods |
| Abstraction | Expose only relevant interface | Interfaces, abstract classes |
| Inheritance | Derive specialized classes from base | is-a relationship |
| Polymorphism | One interface, multiple implementations | Method override, duck typing |
S: Single Responsibility Principle
Definition: A class should have only one reason to change. Every class should do exactly one thing.
Violation:
# WRONG: UserService does too many things
class UserService:
def create_user(self, user_data):
self.db.insert(user_data) # data access
self.email.send_welcome(user_data['email']) # email
self.logger.log(f"User created") # logging
self.analytics.track('signup') # analytics
Fix:
# RIGHT: Each class has one job
class UserRepository:
def create(self, user_data):
return self.db.insert(user_data)
class WelcomeEmailService:
def send(self, email):
return self.email_client.send_welcome(email)
class UserService:
def __init__(self, repo, email_service):
self.repo = repo
self.email_service = email_service
def create_user(self, user_data):
user = self.repo.create(user_data)
self.email_service.send(user_data['email'])
return user
Why it matters: When the email format changes, only WelcomeEmailService changes. When the DB schema changes, only UserRepository changes. Changes stay isolated.
O: Open/Closed Principle
Definition: Classes should be open for extension but closed for modification. Add new behavior without changing existing code.
Violation:
# WRONG: Every new payment type requires modifying this class
class PaymentProcessor:
def process(self, payment_type, amount):
if payment_type == 'credit_card':
return self._charge_card(amount)
elif payment_type == 'upi':
return self._process_upi(amount)
elif payment_type == 'crypto': # NEW: requires modifying this class
return self._process_crypto(amount)
# Every new type = modify existing code = risk breaking existing types
Fix:
# RIGHT: Strategy pattern. New payment type = new class, existing code untouched.
from abc import ABC, abstractmethod
class PaymentStrategy(ABC):
@abstractmethod
def process(self, amount):
pass
class CreditCardPayment(PaymentStrategy):
def process(self, amount):
print(f"Processing card payment: {amount}")
class UPIPayment(PaymentStrategy):
def process(self, amount):
print(f"Processing UPI payment: {amount}")
class CryptoPayment(PaymentStrategy): # ADD: new file, no modification
def process(self, amount):
print(f"Processing crypto payment: {amount}")
class PaymentProcessor:
def process(self, strategy: PaymentStrategy, amount):
return strategy.process(amount)
Why it matters: Adding new payment types (or discount types, shipping methods, report formats) never risks breaking existing functionality.
L: Liskov Substitution Principle
Definition: Objects of a subclass must be substitutable for objects of the superclass without breaking the program.
Violation:
# WRONG: Square violates Rectangle's behavior
class Rectangle:
def set_width(self, w):
self.width = w
def set_height(self, h):
self.height = h
def area(self):
return self.width * self.height
class Square(Rectangle):
def set_width(self, w):
self.width = w
self.height = w # VIOLATION: silently changes height too
def set_height(self, h):
self.height = h
self.width = h # VIOLATION: silently changes width too
# Code that works for Rectangle breaks for Square:
def resize_rectangle(rect):
rect.set_width(5)
rect.set_height(10)
assert rect.area() == 50 # Fails for Square: area = 100
Fix: Do not use inheritance for is-a relationships that are not truly substitutable. Use composition or a common interface.
class Shape(ABC):
@abstractmethod
def area(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Square(Shape):
def __init__(self, side):
self.side = side
def area(self):
return self.side * self.side
Why it matters: LSP violations create subtle bugs that only appear when base class code is called with subclass objects. Violating LSP makes polymorphism unreliable.
I: Interface Segregation Principle
Definition: Clients should not be forced to depend on interfaces they do not use. Many small interfaces are better than one fat interface.
Violation:
# WRONG: One fat interface forces all implementors to handle everything
class Animal:
def eat(self): pass
def sleep(self): pass
def fly(self): pass # What about dogs?
def swim(self): pass # What about eagles?
def run(self): pass
class Dog(Animal):
def fly(self): # Dog cannot fly
raise NotImplementedError # VIOLATION: forced to implement unused method
Fix:
# RIGHT: Separate interfaces for separate capabilities
class CanEat(ABC):
@abstractmethod
def eat(self): pass
class CanFly(ABC):
@abstractmethod
def fly(self): pass
class CanSwim(ABC):
@abstractmethod
def swim(self): pass
class Dog(CanEat):
def eat(self): print("Dog eats")
# No fly, no swim - Dog doesn't implement what it cannot do
class Duck(CanEat, CanFly, CanSwim):
def eat(self): print("Duck eats")
def fly(self): print("Duck flies")
def swim(self): print("Duck swims")
Why it matters: Fat interfaces create coupling. If the fly() method signature changes, Dog must be updated even though Dog cannot fly. Segregation keeps classes independent.
D: Dependency Inversion Principle
Definition: High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details.
Violation:
# WRONG: UserService directly depends on MySQL implementation
class MySQLDatabase:
def save(self, data):
print(f"Saving to MySQL: {data}")
class UserService:
def __init__(self):
self.db = MySQLDatabase() # VIOLATION: hardcoded dependency
def create_user(self, user):
self.db.save(user)
# Switching to PostgreSQL requires changing UserService
Fix:
# RIGHT: Depend on abstraction (interface), inject the implementation
class DatabaseInterface(ABC):
@abstractmethod
def save(self, data):
pass
class MySQLDatabase(DatabaseInterface):
def save(self, data):
print(f"Saving to MySQL: {data}")
class PostgreSQLDatabase(DatabaseInterface):
def save(self, data):
print(f"Saving to PostgreSQL: {data}")
class UserService:
def __init__(self, db: DatabaseInterface): # Inject dependency
self.db = db
def create_user(self, user):
self.db.save(user)
# Wiring at startup:
db = PostgreSQLDatabase() # or MySQLDatabase() or MockDatabase() for tests
service = UserService(db)
Why it matters: Dependency injection makes UserService testable (inject a mock), swappable (change DB without touching service), and decoupled from infrastructure.
DRY, KISS, YAGNI
Beyond SOLID, these three principles complete the design vocabulary:
| Principle | Rule | Violation example |
|---|---|---|
| DRY (Don't Repeat Yourself) | Every piece of knowledge in one place | Copying validation logic across 5 controllers |
| KISS (Keep It Simple) | Simplest solution that works | Using abstract factory when a simple function works |
| YAGNI (You Ain't Gonna Need It) | Do not add functionality until needed | Building a plugin system for a feature no one asked for |
Design Smell: God Class
A God Class violates SRP maximally. It has hundreds of methods spanning database access, business logic, email, logging, and API calls in one class. The test is: can you describe what this class does in one sentence? If not, split it.
Recognizing God Classes and proposing a split is a high-signal answer in LLD interviews. Interviewers at companies like Swiggy and Razorpay specifically look for this.
Design Smell: Shotgun Surgery
When one change requires modifying many classes at once, it indicates poor cohesion. The fix is usually to consolidate related behavior into one class (the inverse of SRP violations - this is under-cohesion, not over-responsibility).
Composition Over Inheritance
A common design guideline: prefer composition (has-a) over inheritance (is-a) when in doubt.
# Inheritance risk: tight coupling, fragile base class problem
class Car(Vehicle): # what if Vehicle changes?
# Composition: loose coupling
class Car:
def __init__(self, engine, transmission):
self.engine = engine # has-an Engine
self.transmission = transmission # has-a Transmission
Prefer inheritance only when LSP is cleanly satisfied and the is-a relationship is genuinely stable.
The Classic LSP Violation: Rectangle and Square
The most-asked Liskov Substitution question is the Rectangle/Square problem. Mathematically a square is a rectangle, so inheritance feels natural. But it breaks substitutability.
# WRONG: Square inherits Rectangle but breaks the contract
class Rectangle:
def __init__(self, w, h):
self._w = w
self._h = h
def set_width(self, w): self._w = w
def set_height(self, h): self._h = h
def area(self): return self._w * self._h
class Square(Rectangle):
def set_width(self, w):
self._w = w
self._h = w # forced to keep sides equal -> breaks contract
def set_height(self, h):
self._w = h
self._h = h
def stretch_and_check(rect: Rectangle):
rect.set_width(5)
rect.set_height(4)
assert rect.area() == 20 # passes for Rectangle, FAILS for Square (16)
A function written against Rectangle assumes setting width and height independently yields width times height. Square silently violates this, so a Square cannot stand in for a Rectangle without breaking callers. The fix is to not use inheritance here: model both as immutable Shape implementations with an area method, and drop the mutable setters that created the impossible contract.
# RIGHT: no inheritance trap, both satisfy a Shape interface
class Shape(ABC):
@abstractmethod
def area(self): pass
class Rectangle(Shape):
def __init__(self, w, h): self.w, self.h = w, h
def area(self): return self.w * self.h
class Square(Shape):
def __init__(self, side): self.side = side
def area(self): return self.side ** 2
The lesson to state in the interview: an is-a relationship in mathematics is not always an is-a relationship in code. LSP is about behavioral substitutability, not taxonomy. If a subclass weakens a guarantee the base class made, it is not a valid subtype.
The Interface Segregation Violation in Code
# WRONG: a fat interface forces classes to implement methods they cannot support
class Worker(ABC):
@abstractmethod
def work(self): pass
@abstractmethod
def eat(self): pass
class Robot(Worker):
def work(self): return "building"
def eat(self): raise NotImplementedError # robots do not eat -> smell
# RIGHT: split into role-specific interfaces
class Workable(ABC):
@abstractmethod
def work(self): pass
class Eatable(ABC):
@abstractmethod
def eat(self): pass
class Human(Workable, Eatable):
def work(self): return "coding"
def eat(self): return "lunch"
class Robot(Workable):
def work(self): return "building"
The Robot no longer carries a meaningless eat method. Interface Segregation says many small, role-specific interfaces beat one large interface, because no class should be forced to depend on methods it does not use. The telltale smell is a method that throws NotImplementedError, which means the interface is too broad for that implementer.
Follow-up Questions Interviewers Ask
Which SOLID principle is most often violated in real codebases? Single Responsibility, by a wide margin. Classes accrete responsibilities over time until they become God Classes. The second most common is Open/Closed, visible as long if-else or switch chains on a type field that grow with every new case.
How do Open/Closed and Dependency Inversion relate? They are complementary. Dependency Inversion (depend on an abstraction) is the mechanism that makes Open/Closed (extend without modifying) achievable. You inject a PricingStrategy abstraction, and then adding a new pricing model is a new class, not an edit to the consumer.
Can you over-apply SOLID? Yes. Splitting every class into tiny single-method classes produces a maze of indirection that is harder to follow than the problem warrants. SOLID serves maintainability, not ceremony. KISS and YAGNI are the counterweights: apply a principle when the change it guards against is actually likely.
How does composition over inheritance connect to LSP? Composition sidesteps the fragile-base-class and substitutability problems entirely because there is no inheritance contract to violate. When an is-a relationship is shaky (like Square and Rectangle), favoring composition or a shared interface avoids the LSP trap altogether.
How to Use SOLID in an LLD Interview
The strongest LLD answers weave SOLID violations into the design narration: "I am separating pricing logic into a PricingStrategy interface because adding a new pricing model should not require modifying the existing ParkingLot class. That is the Open/Closed principle. I am injecting the strategy rather than instantiating it inside ParkingLot because ParkingLot should not know which pricing model is active at runtime. That is Dependency Inversion."
This shows you understand why the principles exist, not just their names.
Related Articles
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 Topics & Practice
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 →More from PapersAdda
Amazon Leadership Principles Interview 2026: All 16 + STAR
Airbnb Interview Questions 2026: Top Tech, HR & Behavioural Q&As for Freshers
Airtel Interview Questions 2026: Top Tech, HR & Behavioural Q&As for Freshers
AMD Interview Questions 2026: Top Tech, HR & Behavioural Q&As for Freshers