issue 117apr 27mmxxvi
est. 2017
Sun, 27 Apr 2026
vol. IX · no. 117
PapersAdda
placement intelligence, since 2017
640+ briefs · 24 campuses · by reservation
verified offers · sourced from r/developersIndia
razorpay₹65.00 LPA· iit-d · sde-1google₹54.00 LPA· iiit-h · swe-imicrosoft₹49.50 LPA· iit-b · sdeatlassian₹38.00 LPA· nit-w · sde-1amazon₹44.20 LPA· bits-p · sde-1uber₹42.00 LPA· iit-kgp · sde-1razorpay₹65.00 LPA· iit-d · sde-1google₹54.00 LPA· iiit-h · swe-imicrosoft₹49.50 LPA· iit-b · sdeatlassian₹38.00 LPA· nit-w · sde-1amazon₹44.20 LPA· bits-p · sde-1uber₹42.00 LPA· iit-kgp · sde-1

OOPs Design Principles SOLID 2026: Interview Guide with Examples

10 min read
Topics & Practice
Updated: 8 Jun 2026
Aditya Sharma
Aditya's Edit

PapersAdda 2026 Placement Cycle

By Aditya Sharma·Founder & Editor, PapersAdda

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

PillarDefinitionKeyword
EncapsulationBundle data and behavior; hide internalsPrivate fields, public methods
AbstractionExpose only relevant interfaceInterfaces, abstract classes
InheritanceDerive specialized classes from baseis-a relationship
PolymorphismOne interface, multiple implementationsMethod 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:

PrincipleRuleViolation example
DRY (Don't Repeat Yourself)Every piece of knowledge in one placeCopying validation logic across 5 controllers
KISS (Keep It Simple)Simplest solution that worksUsing abstract factory when a simple function works
YAGNI (You Ain't Gonna Need It)Do not add functionality until neededBuilding 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.


Methodology applied to this articlelast verified 8 Jun 2026
Sources used
Public exam-pattern documents, official recruiter pages, and verified candidate reports on r/developersIndia and LinkedIn.
Verification window
Page last edited 8 Jun 2026 by Aditya Sharma. Numbers and patterns sanity-checked against the most recent 2026 cycle drives we tracked.
What we did NOT do
  • 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.
Verification policy: /editorial-standards/. Found something incorrect? Submit a correction - we respond within 48 hours.

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

Share this guide: