← Back to Article List         
SOLID Principles with examples

SOLID Principles with examples

Published on 13 Jun 2026     11 min read Design Principles & Patterns
SOLID Principles

What is SOLID principles?

SOLID is a set of five design principles used in object-oriented programming (OOP) to make software easier to understand, maintain, and extend.

Principle

Focus

Goal

S

Single Responsibility

One class → one purpose

O

Open/Closed

Extend, don’t modify

L

Liskov Substitution

Subclasses behave like parents

I

Interface Segregation

Small, focused interfaces

D

Dependency Inversion

Depend on abstractions

 

SRP – Single Responsibility Principle.

One class should do only one thing.

A class should have only one reason to change — meaning it should do only one specific job or responsibility.

 

OCP – Open Close Principle.

A class should be open for extension but closed for modification.

 

LSP – Liskov Substitution Principle

 

Simple Definition :- The child class should be able to implement all the method of the parent class seamlessly and smoothly.

Subclasses should not change the expected behaviour of their parent class.

 

 

ISP – Interface Segregation Principle

 

Client should not be forced to use Methods which it does not need.

 

Definition:
Clients (classes that use interfaces) should not be forced to depend on methods they don’t use.

 

 

DI – Dependency Inversion

 

High-level modules should not depend on low-level modules.
Both should depend on abstractions (like interfaces or abstract classes).

Instead of classes depending directly on other concrete classes,
they should depend on interfaces or abstract classes.

 

 

SRP – Single Responsibility Principle – Example.

One class should do only one thing.

A class should have only one reason to change — meaning it should do only one specific job or responsibility.

Example:

public class Employee

{

public int Id { get; set; }

public string Name { get; set; }

public int Type { get; set; }

// Manager, Helper, Team lead

public double CalculateSalary()

{

return 0;

}

public void DoTask()

{

// task of the employee

}

}

 

As per the above example, employee class should not do salaryCalcuation. It should be in SalaryCalculation class. Employee class should do employee related jobs or actions.

public class Salary Calculation

{

public double CalculateSalary(Employee e)

{

return 0;

}

}

OCP – Open Close Principle – Example.

A class should be open for extension but closed for modification.

Example

public class PaymentProcessor

{

    public void ProcessPayment(string paymentType)

    {

        if (paymentType == "CreditCard")

        {

            Console.WriteLine("Processing credit card payment...");

        }

        else if (paymentType == "PayPal")

        {

            Console.WriteLine("Processing PayPal payment...");

        }

    }

}

 

 

Problem:
If we need to add a new payment type (e.g., UPI), we have to modify this class — violating OCP.

 

With OCP (good design):

public interface IPayment

{

    void ProcessPayment();

}

 

public class CreditCardPayment : IPayment

{

    public void ProcessPayment()

    {

        Console.WriteLine("Processing credit card payment...");

    }

}

 

public class PayPalPayment : IPayment

{

    public void ProcessPayment()

    {

        Console.WriteLine("Processing PayPal payment...");

    }

}

 

public class PaymentProcessor

{

    public void Process(IPayment payment)

    {

        payment.ProcessPayment();

    }

}

 

Now, if you want to add a new payment method (like UPI), just create a new class:

public class UpiPayment : IPayment

{

    public void ProcessPayment()

    {

        Console.WriteLine("Processing UPI payment...");

    }

}

 

You don’t touch existing code — just extend it with new behavior.

 

Key Point:

Modify your code by adding new classes, not by editing existing ones.

This keeps your system stable, easier to maintain, and less prone to bugs.

 

LSP – Liskov Substitution Principle  – Example.

 

Simple Definition :- The child class should be able to implement all the method of the parent class seamlessly and smoothly.

 

Subclasses should not change the expected behaviour of their parent class.

 

Without LSP (bad design):

 

public class Bird

{

    public virtual void Fly()

    {

        Console.WriteLine("Bird is flying");

    }

}

 

public class Sparrow : Bird

{

    public override void Fly()

    {

        Console.WriteLine("Sparrow is flying");

    }

}

 

public class Penguin : Bird

{

    public override void Fly()

    {

        throw new NotImplementedException("Penguins can’t fly!");

    }

}

 

Problem:
Penguin is a type of Bird, but when used as a Bird, it breaks the logic because Fly() throws an error.
So, Penguin violates the Liskov Substitution Principle.

 

With LSP (good design):

public abstract class Bird

{

    public abstract void Eat();

}

 

public interface IFlyingBird

{

    void Fly();

}

 

public class Sparrow : Bird, IFlyingBird

{

    public override void Eat() => Console.WriteLine("Sparrow is eating");

    public void Fly() => Console.WriteLine("Sparrow is flying");

}

 

public class Penguin : Bird

{

    public override void Eat() => Console.WriteLine("Penguin is eating");

}

 

Now:

  • Bird defines common behavior (Eat()).
  • Only flying birds implement IFlyingBird.

No broken expectations — LSP is followed.

Key Point:

Subclasses should not change the expected behavior of their parent class.

 

ISP – Interface Segregation Principle – Example.

 

Client should not be forced to use Methods which it does not need.

 

Definition:
Clients (classes that use interfaces) should not be forced to depend on methods they don’t use.

 

Without ISP (bad design):

public interface IMachine

{

    void Print();

    void Scan();

    void Fax();

}

 

public class SimplePrinter : IMachine

{

    public void Print()

    {

        Console.WriteLine("Printing document...");

    }

 

    public void Scan()

    {

        // Not needed

        throw new NotImplementedException();

    }

 

    public void Fax()

    {

        // Not needed

        throw new NotImplementedException();

    }

}

 

Problem:
SimplePrinter only needs Print(), but is forced to implement Scan() and Fax() — violating ISP.

 

With ISP (good design):

 

public interface IPrinter

{

    void Print();

}

 

public interface IScanner

{

    void Scan();

}

 

public interface IFax

{

    void Fax();

}

 

public class SimplePrinter : IPrinter

{

    public void Print()

    {

        Console.WriteLine("Printing document...");

    }

}

 

public class MultiFunctionPrinter : IPrinter, IScanner, IFax

{

    public void Print() => Console.WriteLine("Printing...");

    public void Scan() => Console.WriteLine("Scanning...");

    public void Fax() => Console.WriteLine("Faxing...");

}

 

Now:

  • SimplePrinter implements only what it needs (IPrinter).
  • MultiFunctionPrinter implements all (IPrinter, IScanner, IFax).

This follows the Interface Segregation Principle.

 

 

 

 

DI – Dependency Inversion – Example.

 

High-level modules should not depend on low-level modules.
Both should depend on abstractions (like interfaces or abstract classes).

Instead of classes depending directly on other concrete classes,
they should depend on interfaces or abstract classes.

 

Without DIP (bad design):

public class EmailService

{

    public void SendEmail(string message)

    {

        Console.WriteLine("Sending email: " + message);

    }

}

 

public class Notification

{

    private EmailService _emailService = new EmailService();

 

    public void NotifyUser()

    {

        _emailService.SendEmail("Welcome user!");

    }

}

 

Problem:

  • Notification is tightly coupled to EmailService.
  • If you later want to use SMS, you must change the Notification class — violating DIP.

 

With DIP (good design):

 

public interface IMessageService

{

    void SendMessage(string message);

}

 

public class EmailService : IMessageService

{

    public void SendMessage(string message)

    {

        Console.WriteLine("Sending email: " + message);

    }

}

 

public class SMSService : IMessageService

{

    public void SendMessage(string message)

    {

        Console.WriteLine("Sending SMS: " + message);

    }

}

 

public class Notification

{

    private readonly IMessageService _messageService;

 

    public Notification(IMessageService messageService)

    {

        _messageService = messageService;

    }

 

    public void NotifyUser()

    {

        _messageService.SendMessage("Welcome user!");

    }

}

 

using System;

 

class Program

{

    static void Main()

    {

        // Choose which implementation to inject

        IMessageService service = new EmailService();

        // IMessageService service = new SmsService();

 

        NotificationManager manager = new NotificationManager(service);

        manager.Send("Hello using Dependency Injection!");

    }

}

Now:

  • Notification depends on the interface IMessageService, not on a specific implementation.
  • We can inject any message service (Email, SMS, WhatsApp, etc.) without changing Notification.

This follows Dependency Inversion Principle.

 

 

 

Relation to Dependency Injection (DI)

·         DIP is a principle (a rule: depend on abstractions, not concretes).

·         Dependency Injection (DI) is a technique (how we pass the dependencies — via constructor, property, or method).

In short:

DIP is the “rule.”
DI is one way to “follow the rule.”

Key Benefits of DIP

·         Loosely coupled code

·         Easy to extend and maintain

·         Easier to unit test (can mock interfaces)

·         Promotes reusable components

 

 

 

Interview Question:

General SOLID Questions

Q1. What does SOLID stand for?
A:

·         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)


Q2. Why are SOLID principles important?
A: They make code:

·         Maintainable

·         Flexible and extendable

·         Easy to test

·         Less prone to bugs


Q3. Can you give a real-world example of SOLID?
A: A payment system:

·         Each class has a single responsibility (SRP).

·         New payment methods can be added without modifying existing code (OCP).

·         Subclasses behave like their parent (LSP).

·         Clients depend only on needed interfaces (ISP).

·         High-level modules depend on abstractions, not concretes (DIP).


Single Responsibility Principle (SRP)

Q4. What is SRP?
A: A class should have only one reason to change — it should do one job only.


Q5. How do you identify SRP violations?
A: If a class handles multiple unrelated tasks (e.g., generating reports and saving them to DB), it violates SRP.


Q6. Example of SRP in real-world apps?
A:

·         InvoiceGenerator → generates invoices

·         InvoicePrinter → prints invoices

·         InvoiceRepository → saves invoices to DB


Q7. How does SRP improve maintainability?
A: Changes in one responsibility don’t affect other parts of the class, reducing bugs.


Q8. How to refactor a class with multiple responsibilities?
A: Split it into multiple classes, each handling a single responsibility.


Open/Closed Principle (OCP)

Q9. What is OCP?
A: Classes should be open for extension, closed for modification.


Q10. How to design OCP-compliant classes?
A: Use interfaces, abstract classes, or polymorphism so new functionality can be added without changing existing code.


Q11. Example of OCP in projects?
A: Adding a new payment method without modifying PaymentProcessor, by implementing a new IPayment interface.


Q12. How do interfaces/abstract classes help OCP?
A: They allow new classes to extend behavior without modifying existing code.


Q13. Problems if OCP is not followed?
A: Existing code must be modified for new features → introduces bugs and reduces maintainability.


Liskov Substitution Principle (LSP)

Q14. What is LSP?
A: Subclasses should be replaceable by their parent class without breaking the program.


Q15. How to check LSP violation?
A: If replacing a parent class with a subclass causes errors or unexpected behavior, LSP is violated.


Q16. Real-world LSP example?
A: Bird class with a Fly() method. Penguin should not inherit Fly(), or use a separate interface like IFlyingBird.


Q17. Why is throwing exceptions in overridden methods an LSP violation?
A: It breaks the expectation of the parent class behavior.


Q18. How does LSP relate to inheritance/polymorphism?
A: Ensures that derived classes behave consistently with base class contracts.


Interface Segregation Principle (ISP)

Q19. What is ISP?
A: Clients should not be forced to depend on methods they don’t use.


Q20. How to decide interface size/scope?
A: Each interface should focus on one group of related actions.


Q21. Example of ISP violation?
A: An IMachine interface with Print(), Scan(), Fax() — a simple printer implementing it has to throw exceptions for unused methods.


Q22. How does ISP improve maintainability/testability?
A: Smaller, focused interfaces reduce unnecessary dependencies, making code easier to test and maintain.


Q23. How does ISP help in multi-team projects?
A: Teams can implement only the interfaces they need, avoiding unnecessary coupling.


Dependency Inversion Principle (DIP)

Q24. What is DIP?
A: High-level modules should depend on abstractions, not concrete classes.


Q25. How does DIP differ from DI?
A: DIP is a principle; DI (Dependency Injection) is a technique to follow that principle.


Q26. Example of DIP in code?

public interface IMessageService { void SendMessage(string msg); }
public class EmailService : IMessageService { ... }
public class Notification
{
    private readonly IMessageService _service;
    public Notification(IMessageService service) { _service = service; }
    public void Notify() => _service.SendMessage("Hi");
}

Here, Notification depends on abstraction IMessageService, not concrete EmailService.


Q27. How does DIP improve flexibility/testability?
A: You can swap implementations (Email, SMS) or mock dependencies for unit tests without changing the high-level module.


Q28. How do abstractions reduce coupling?
A: High-level modules interact with interfaces, not concrete classes, so low-level changes don’t affect them.


Q29. Real-world analogy for DIP?
A: A remote control (high-level) works with any TV brand (low-level) through a standard interface — you don’t need to rewrite the remote for each TV.