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...");
}
}
Everything works.
A month later, the business wants to support PayPal.
So we modify the function.
if (type === "paypal") {
...
}
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...");
}
}
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;
}
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...");
}
}
The checkout process no longer needs to know which payment method it's dealing with.
function checkout(payment: PaymentMethod, amount: number) {
payment.process(amount);
}
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...");
}
}
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
Now:
DiscountService
↓
knows only DiscountStrategy
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)