DEV Community

Cover image for Open/Closed Principle (OCP): How Do We Add Features Without Breaking Existing Code?
Ashay Tiwari
Ashay Tiwari

Posted on

Open/Closed Principle (OCP): How Do We Add Features Without Breaking Existing Code?

OCP

In the previous article, we discussed the Single Responsibility Principle (SRP) and learned that a class should have one clear reason to change.

Now let's look at the second SOLID principle:

Open/Closed Principle (OCP).

This principle is often summarized as:

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

It's a famous definition.

It's also one of the most misunderstood.

The obvious question is:

How can something be open and closed at the same time?

Let's start with the problem.


The Problem

Imagine you're building an e-commerce application.

Initially, the business only supports two payment methods:

  • Credit Card
  • UPI

Your implementation might look like this:

function processPayment(type: string, amount: number) {
  if (type === "credit-card") {
    console.log("Processing credit card payment...");
  } else if (type === "upi") {
    console.log("Processing UPI payment...");
  }
}
Enter fullscreen mode Exit fullscreen mode

Everything works.

A month later, the business wants to support PayPal.

So we modify the function.

if (type === "paypal") {
  ...
}
Enter fullscreen mode Exit fullscreen mode

A few weeks later:

  • Apple Pay
  • Bank Transfer
  • Gift Cards

Every new payment method requires editing the same function.

Again.

And again.

And again.


What's the Real Problem?

At first glance, modifying the function doesn't seem like a problem.

After all, adding new functionality usually means changing code.

But consider this.

That payment function is already:

  • Tested.
  • Used by multiple parts of the application.
  • Running successfully in production.

Every modification introduces risk.

A small mistake while adding Apple Pay could accidentally break UPI payments.

The more frequently we modify stable code, the greater the chance of introducing bugs.

The problem isn't adding new features.

The problem is having to modify existing, working code every time a new requirement appears.


The Idea Behind OCP

The Open/Closed Principle encourages us to design software so that new behaviour can be added without modifying existing, tested code.

Notice the wording.

It doesn't say:

Never modify code.

That's unrealistic.

Instead, it asks:

Can we design our code so new features are added by extending it rather than editing it?

That's a subtle but powerful shift in thinking.

Question

If you were designing this using what you've already learned about:

  • Abstraction
  • Interfaces
  • Polymorphism

How would you avoid the growing chain of:

function processPayment(type: string, amount: number) {
  if (type === "credit-card") {
    console.log("Processing credit card payment...");
  } else if (type === "upi") {
    console.log("Processing UPI payment...");
  }
}
Enter fullscreen mode Exit fullscreen mode

A Better Approach

Instead of one function knowing about every payment method, each payment method can be responsible for processing itself.

Applying Polymorphism

interface PaymentMethod {
  process(amount: number): void;
}
Enter fullscreen mode Exit fullscreen mode

Now we create separate implementations.

class CreditCardPayment implements PaymentMethod {
  process(amount: number) {
    console.log("Processing credit card payment...");
  }
}

class UpiPayment implements PaymentMethod {
  process(amount: number) {
    console.log("Processing UPI payment...");
  }
}
Enter fullscreen mode Exit fullscreen mode

The checkout process no longer needs to know which payment method it's dealing with.

function checkout(payment: PaymentMethod, amount: number) {
  payment.process(amount);
}
Enter fullscreen mode Exit fullscreen mode

Now suppose the business wants to support PayPal.

Do we modify checkout()?

No.

We simply create another implementation.

class PayPalPayment implements PaymentMethod {
  process(amount: number) {
    console.log("Processing PayPal payment...");
  }
}
Enter fullscreen mode Exit fullscreen mode

The existing checkout logic remains untouched.

We've extended the system without modifying stable code.

That's exactly what OCP is encouraging.

What Changed?

Previously:

DiscountService
   ↓
knows all discount types
Enter fullscreen mode Exit fullscreen mode

Now:

DiscountService
   ↓
knows only DiscountStrategy
Enter fullscreen mode Exit fullscreen mode

That's abstraction.


Why Is This Useful?

Following OCP provides several benefits.

  • Less Risk
  • Existing, tested code remains unchanged.
  • Easier Feature Development
  • New functionality is added instead of squeezed into existing logic.
  • Better Team Collaboration
  • Different developers can work on new features without constantly editing the same files.
  • Improved Maintainability
  • The system naturally grows by adding new pieces instead of making one central component increasingly complex.

Does OCP Mean Never Modify Existing Code?

Not at all.

This is probably the biggest misconception.

Sometimes modifying existing code is absolutely the right thing to do.

If you discover:

  • A bug
  • Poor design
  • Duplicate logic
  • Performance issues

You should improve it.

OCP isn't about avoiding modifications at all costs.

It's about designing systems where expected future changes are handled through extension rather than repeatedly editing the same code.


The Trade-offs

Like every design principle, OCP isn't free.

Supporting extension often means:

  • More interfaces.
  • More abstractions.
  • More classes.

If your application is small or unlikely to change, these extra layers may provide little value.

The goal isn't to predict every future requirement.

It's to recognise areas of the system that are likely to evolve and design those areas thoughtfully.


What's Next?

We've now explored two SOLID principles.

SRP taught us to give each class one clear responsibility.

OCP taught us how to extend behaviour without repeatedly modifying stable code.

Next, we'll tackle one of the most debated principles in software design:

The Liskov Substitution Principle (LSP).

We'll answer an interesting question:

Just because one class inherits from another, does it really mean it can replace it?

Top comments (0)