← Back to Article List         
Open/Closed Principle (OCP)

Open/Closed Principle (OCP)

Published on 28 Sep 2026     12 min read SOLID Principles
SOLID Principles

Open/Closed Principle (OCP)

1. What is the Open/Closed Principle?

Open/Closed Principle (OCP) is the second principle of SOLID.

Software entities should be open for extension, but closed for modification.

In simple English:

We should be able to add new functionality without repeatedly changing existing, stable code.

Here, software entities can mean:

  • Classes
  • Methods
  • Modules
  • Components

2. What does "Open" and "Closed" mean?

This is the most important part to understand.

Open for Extension

We should be able to add new behavior.

For example, an application currently supports:

Email
SMS

Tomorrow, the customer asks for:

WhatsApp

We should be able to add WhatsApp support.

So:

Open = New functionality can be added.

Closed for Modification

Adding WhatsApp should ideally not require repeatedly editing the existing notification-processing logic.

So:

Closed = Existing stable code should not need modification for every new variant.

Therefore:

Open for Extension
        +
Closed for Modification
        =
Open/Closed Principle

3. Why do we need OCP?

Suppose we have an e-commerce application.

Customers can receive notifications through:

Email
SMS

We create a NotificationService containing conditions:

if (type == "email")
{
    // Send Email
}
else if (type == "sms")
{
    // Send SMS
}

Later, the business asks:

Add WhatsApp notification.

We modify the same class:

else if (type == "whatsapp")
{
    // Send WhatsApp
}

Later:

Add Push Notification.

Again:

else if (type == "push")
{
    // Send Push Notification
}

Later:

Add Teams notification.

Again we modify the class.

Our code starts growing:

NotificationService
       │
       ├── if Email
       ├── else if SMS
       ├── else if WhatsApp
       ├── else if Push
       ├── else if Teams
       └── ...

Every new notification type requires us to modify the same existing logic.

That is the problem OCP tries to solve.


4. Before OCP — Bad Design

Let's first create a simple program without OCP.

using System;

public class NotificationService
{
    public void SendNotification(
        string type,
        string message)
    {
        if (type == "email")
        {
            Console.WriteLine(
                $"Email sent: {message}");
        }
        else if (type == "sms")
        {
            Console.WriteLine(
                $"SMS sent: {message}");
        }
    }
}

public class Program
{
    public static void Main()
    {
        NotificationService service =
            new NotificationService();

        service.SendNotification(
            "email",
            "Order placed successfully");

        service.SendNotification(
            "sms",
            "Order placed successfully");
    }
}

Output

Email sent: Order placed successfully
SMS sent: Order placed successfully

The program works.

But there is a design problem.


5. What is the Problem?

Currently:

NotificationService
       │
       ├── Email
       │
       └── SMS

Now the customer says:

We need WhatsApp.

We have to open NotificationService and change it:

else if (type == "whatsapp")
{
    Console.WriteLine(
        $"WhatsApp sent: {message}");
}

Now:

NotificationService
       │
       ├── Email
       ├── SMS
       └── WhatsApp

Later Push Notification:

NotificationService
       │
       ├── Email
       ├── SMS
       ├── WhatsApp
       └── Push

Every new type means:

New Requirement
      ↓
Open NotificationService
      ↓
Modify Existing Code
      ↓
Retest Existing Functionality
      ↓
Possible Regression Bug

This design is not very extensible.


6. Apply Open/Closed Principle

Instead of putting every notification type inside one conditional method, we create an abstraction:

INotification

Then each notification type provides its own implementation.

                 INotification
                       ↑
          ┌────────────┼────────────┐
          │            │            │
 EmailNotification SmsNotification WhatsAppNotification

Now new notification behavior can be introduced as another implementation.


7. Full Simple Program Using OCP

using System;

// Abstraction
public interface INotification
{
    void Send(string message);
}


// Email implementation
public class EmailNotification : INotification
{
    public void Send(string message)
    {
        Console.WriteLine(
            $"Email sent: {message}");
    }
}


// SMS implementation
public class SmsNotification : INotification
{
    public void Send(string message)
    {
        Console.WriteLine(
            $"SMS sent: {message}");
    }
}


// WhatsApp implementation
public class WhatsAppNotification : INotification
{
    public void Send(string message)
    {
        Console.WriteLine(
            $"WhatsApp sent: {message}");
    }
}


// Notification service
public class NotificationService
{
    public void SendNotification(
        INotification notification,
        string message)
    {
        notification.Send(message);
    }
}


// Program
public class Program
{
    public static void Main()
    {
        NotificationService service =
            new NotificationService();

        INotification email =
            new EmailNotification();

        service.SendNotification(
            email,
            "Order placed successfully");


        INotification sms =
            new SmsNotification();

        service.SendNotification(
            sms,
            "Order placed successfully");


        INotification whatsapp =
            new WhatsAppNotification();

        service.SendNotification(
            whatsapp,
            "Order placed successfully");
    }
}

Output

Email sent: Order placed successfully
SMS sent: Order placed successfully
WhatsApp sent: Order placed successfully

8. Program Explanation

Let's understand each part.

Step 1 — Create the Abstraction

public interface INotification
{
    void Send(string message);
}

INotification defines a contract.

It says:

Any notification implementation must provide a Send() method.

It doesn't care how the notification is sent.

For example:

INotification
      │
      └── Send()

Different implementations can provide different behavior.


9. Email Notification

public class EmailNotification : INotification
{
    public void Send(string message)
    {
        Console.WriteLine(
            $"Email sent: {message}");
    }
}

EmailNotification implements:

INotification

Its responsibility is to provide the email version of Send().

Conceptually:

INotification
      ↑
      │
EmailNotification
      │
      ↓
Send Email

10. SMS Notification

public class SmsNotification : INotification
{
    public void Send(string message)
    {
        Console.WriteLine(
            $"SMS sent: {message}");
    }
}

The same interface is implemented by SmsNotification.

INotification
      ↑
      │
SmsNotification
      │
      ↓
Send SMS

11. WhatsApp Notification

public class WhatsAppNotification : INotification
{
    public void Send(string message)
    {
        Console.WriteLine(
            $"WhatsApp sent: {message}");
    }
}

Again:

INotification
      ↑
      │
WhatsAppNotification
      │
      ↓
Send WhatsApp

12. NotificationService

Now look carefully at this class:

public class NotificationService
{
    public void SendNotification(
        INotification notification,
        string message)
    {
        notification.Send(message);
    }
}

This is the important part.

Notice that NotificationService doesn't contain:

if (type == "email")

or:

else if (type == "sms")

or:

else if (type == "whatsapp")

Instead, it works with:

INotification

Therefore it doesn't need to know whether the actual object is:

EmailNotification
SmsNotification
WhatsAppNotification
PushNotification
TeamsNotification

It simply says:

notification.Send(message);

The correct implementation executes through polymorphism.


13. How Does the Correct Method Execute?

Suppose:

INotification notification =
    new EmailNotification();

Then:

notification.Send(message);

executes:

EmailNotification.Send()

If:

INotification notification =
    new SmsNotification();

then the same:

notification.Send(message);

executes:

SmsNotification.Send()

If:

INotification notification =
    new WhatsAppNotification();

then it executes:

WhatsAppNotification.Send()

This is polymorphism.


14. Program Flow

For Email:

Program
   │
   ↓
EmailNotification
   │
   ↓
NotificationService
   │
   ↓
INotification.Send()
   │
   ↓
EmailNotification.Send()
   │
   ↓
Email Sent

For SMS:

Program
   │
   ↓
SmsNotification
   │
   ↓
NotificationService
   │
   ↓
INotification.Send()
   │
   ↓
SmsNotification.Send()
   │
   ↓
SMS Sent

The NotificationService code stays the same.


15. Main Benefit — Adding a New Type

Now imagine that after six months the customer says:

We need Push Notification.

We create another implementation:

public class PushNotification : INotification
{
    public void Send(string message)
    {
        Console.WriteLine(
            $"Push notification sent: {message}");
    }
}

That's the new behavior.

Then it can be used as:

INotification push =
    new PushNotification();

service.SendNotification(
    push,
    "Order shipped successfully");

The important point is that we didn't need to add another condition inside:

NotificationService

The service continues to work with:

INotification

16. Why is this "Open for Extension"?

Originally:

INotification
      ↑
 ┌────┴─────┐
 │          │
Email      SMS

Later:

INotification
      ↑
 ┌────┼──────────┐
 │    │          │
Email SMS    WhatsApp

Later:

INotification
      ↑
 ┌────┼──────────┬──────┐
 │    │          │      │
Email SMS    WhatsApp  Push

We can keep adding new implementations.

Therefore:

Open for extension


17. Why is this "Closed for Modification"?

The stable processing method remains:

public void SendNotification(
    INotification notification,
    string message)
{
    notification.Send(message);
}

Whether we add:

Email
SMS
WhatsApp
Push
Teams
Slack

this method does not need a new if/else branch for each one.

Therefore, with respect to this expected variation:

Closed for modification


18. Before OCP vs After OCP

Before OCP

NotificationService
        │
        ├── if Email
        ├── else if SMS
        ├── else if WhatsApp
        └── else if Push

Adding a new notification type means modifying the conditional logic.


After OCP

                  INotification
                        ↑
        ┌───────────────┼───────────────┐
        │               │               │
      Email            SMS          WhatsApp
                                       
Later:

                  INotification
                        ↑
        ┌──────────┬────┼────┬──────────┐
        │          │         │          │
      Email       SMS     WhatsApp     Push

New implementations extend the system.


19. OCP Doesn't Mean "Never Modify Existing Code"

This is an important interview point.

Some developers misunderstand OCP as:

Once a class is created, never modify it.

That is not correct.

Existing code can obviously be changed for:

  • Bug fixes
  • Security fixes
  • Refactoring
  • Changed business requirements
  • Performance improvements

OCP mainly says:

Design the parts that are expected to vary so new variants can usually be added without repeatedly modifying stable logic.


20. Is Using an Interface Automatically OCP?

No.

Creating an interface alone doesn't automatically mean the design follows OCP.

For example, you could have:

public interface INotification
{
    void Send();
}

but somewhere else still write:

if (type == "email")
{
    // ...
}
else if (type == "sms")
{
    // ...
}
else if (type == "whatsapp")
{
    // ...
}

If every new implementation requires modifying important business logic throughout the system, the design may still have an OCP problem.

The goal is:

New variant
     ↓
Add implementation
     ↓
Existing stable processing logic
requires little/no change

21. Real-Time Industry Example

Consider a payment system.

Today:

Credit Card
UPI

Tomorrow:

PayPal

Later:

Apple Pay

A poor design could become:

PaymentService
     │
     ├── if CreditCard
     ├── else if UPI
     ├── else if PayPal
     ├── else if ApplePay
     └── ...

An OCP-oriented design could use:

                  IPayment
                     ↑
          ┌──────────┼──────────┐
          │          │          │
     CreditCard     UPI       PayPal

Later:

                  IPayment
                     ↑
       ┌─────────────┼─────────────┐
       │             │             │
 CreditCard         UPI          PayPal
                                     │
                              + ApplePay

The application is designed around the stable abstraction while individual payment behaviors are extensible.


22. OCP in ASP.NET Core

OCP is commonly seen in ASP.NET Core applications through:

  • Interfaces
  • Dependency Injection
  • Polymorphism
  • Strategy Pattern
  • Factory Pattern
  • Decorator Pattern
  • Middleware
  • Authentication handlers
  • Logging providers

For example:

Controller
    ↓
IPaymentService
    ↑
    │
CreditCardPaymentService

The controller doesn't need to know every technical detail of the implementation.


23. OCP and Dependency Injection

These concepts often work together.

Suppose:

OrderService
      ↓
INotification
      ↑
EmailNotification

ASP.NET Core DI can inject:

EmailNotification

as the implementation of:

INotification

Later, a different implementation can be supplied.

However:

Dependency Injection and OCP are not the same thing.

DI is a mechanism for supplying dependencies.

OCP is a design principle about extensibility and minimizing modification of stable code.


24. OCP and Design Patterns

Several design patterns commonly help implement OCP.

Strategy Pattern

Useful when different algorithms or behaviors can be selected.

Payment Strategy
    ├── CreditCard
    ├── UPI
    └── PayPal

Factory Pattern

Useful for creating the appropriate implementation.

PaymentFactory
      ↓
IPayment

Decorator Pattern

Useful for adding behavior without modifying the original implementation.

Service
   ↓
Logging Decorator
   ↓
Caching Decorator

So SOLID principles and design patterns are closely related, but they are not identical.


25. Advantages of Open/Closed Principle

1. Easy to Extend

New functionality can often be introduced by adding a new class.

Existing:

Email
SMS

New:

WhatsApp

without rewriting the central processing logic.


2. Reduces Regression Risk

If stable code is modified less frequently, there is generally less chance of accidentally breaking existing behavior.


3. Better Maintainability

Instead of a huge conditional block:

if...
else if...
else if...
else if...
else if...

different behaviors are separated into dedicated implementations.


4. Better Testability

Each implementation can be tested independently:

EmailNotification
SmsNotification
WhatsAppNotification

5. Supports Polymorphism

Different implementations can be accessed through the same abstraction:

INotification

6. Supports Future Requirements

If new variants are expected, OCP can make those additions less disruptive.


7. Reduces Tight Coupling

Business logic doesn't need to know every concrete implementation.

Instead of:

NotificationService
      ↓
Email + SMS + WhatsApp + Push

we can have:

NotificationService
      ↓
INotification

26. When Should We Think About OCP?

OCP is especially useful when you notice code such as:

if (type == "A")
{
}
else if (type == "B")
{
}
else if (type == "C")
{
}

or:

switch (type)
{
    case "A":
        break;

    case "B":
        break;

    case "C":
        break;
}

A switch statement is not automatically bad.

But if the same conditional logic keeps changing whenever a new type is introduced, it may indicate an opportunity to apply:

  • OCP
  • Polymorphism
  • Strategy Pattern
  • Factory Pattern

27. Can OCP Be Overused?

Yes.

Suppose your application has one simple requirement:

Send Email

and there is no realistic expectation of multiple notification behaviors.

Creating:

INotification
NotificationBase
NotificationFactory
NotificationStrategy
EmailNotification
NotificationResolver

may create unnecessary complexity.

SOLID principles are design guidelines, not rules requiring maximum abstraction everywhere.

Apply OCP where variation and extension are reasonably expected.


28. Easy Way to Remember OCP

Remember:

        Existing System
              │
              ↓
         Stable Code
              │
        Don't repeatedly
           change it
              │
              ↓
      Add New Implementation
              │
              ↓
        New Functionality

Or simply:

Extend, don't repeatedly modify.


29. Key Points

  • OCP = Open/Closed Principle.
  • It is the O in SOLID.
  • Software should be open for extension.
  • Software should be closed for unnecessary/repeated modification.
  • Open means new behavior can be added.
  • Closed means stable existing logic should not require changes for every new variant.
  • OCP does not mean existing code can never be changed.
  • Interfaces, abstract classes, polymorphism and composition commonly help implement OCP.
  • Dependency Injection can support an OCP-oriented design.
  • Strategy, Factory and Decorator patterns often help achieve OCP.
  • A growing if/else or switch based on behavior type can be a sign to consider OCP, though conditionals themselves are not violations.
  • OCP improves extensibility, maintainability, testability, and change isolation.
  • Don't create unnecessary abstractions where extension is unlikely.

Interview Answer

Open/Closed Principle states that software entities should be open for extension but closed for modification. In simple terms, we should design the application so that new behavior can be added without repeatedly modifying existing stable code. This is commonly achieved using abstractions, polymorphism, composition, and dependency injection. For example, instead of modifying a notification service whenever Email, SMS, WhatsApp, or Push support is added, each notification type can implement a common interface and the existing processing logic can work with that abstraction.