Open/Closed Principle
Think about a power strip. When you get a new gadget, you don't rewire the wall socket — you just plug the gadget in. The socket stays exactly as it was; the strip's capability grows. That's the idea behind the Open/Closed Principle (OCP): software entities should be open for extension, but closed for modification.
💡 The Bottom Line
When you need new behavior, you should be able to add new code — not edit code that already works and is already tested. Every time you modify a working class to handle "one more case," you risk breaking every case that already worked.
The trap: a growing if/else chain
Here's how OCP gets violated, one "just one more case" at a time:
class DiscountCalculator {
calculate(customerType, price) {
if (customerType === "regular") return price;
if (customerType === "member") return price * 0.9;
if (customerType === "vip") return price * 0.8;
// every new customer type means editing this method again
}
}
Every new discount tier means opening this method, editing it, and re-testing every branch that was already there — because you touched the file, you could have broken any of them.
⚠️ Watch Out
OCP doesn't mean "never edit a file again." Fixing an actual bug in existing logic still means editing that logic. OCP is specifically about adding new variants of behavior — that's the part that should be additive, not the part where you fix something broken.
Fixing it: extension instead of modification
Define a shared shape (an interface, or just a common method name), and let each variant be its own class:
class Discount {
apply(price) { return price; } // base case: no discount
}
class MemberDiscount extends Discount {
apply(price) { return price * 0.9; }
}
class VipDiscount extends Discount {
apply(price) { return price * 0.8; }
}
class DiscountCalculator {
calculate(discount, price) {
return discount.apply(price); // never changes, for any discount type
}
}
Adding a StudentDiscount next month means writing one new class. Nothing about
DiscountCalculator, MemberDiscount, or VipDiscount needs to change
— none of them are even touched, so none of them can be broken by the new addition.
🙋 There Are No Dumb Questions
Q: Isn't this just more classes for the same result?
A: Yes, and that's the point. The extra structure is what makes "add a new discount" a
zero-risk operation on existing code instead of a risky edit to a method five other discount
types already depend on.
How this actually gets used
OCP is usually achieved through polymorphism — a shared interface or base class that different implementations plug into — rather than branching logic that checks "what type is this" and reacts differently. Plugin systems, payment gateways, and notification channels (email, SMS, push) are classic real-world examples: adding Slack notifications shouldn't require editing the email-sending code.
🔧 Try It Yourself
Picture a ShapeAreaCalculator with a method that does
if (shape.type === "circle") ... else if (shape.type === "square") .... Sketch how
you'd redesign it so that adding a Triangle shape next month requires writing one new
class and touching zero existing ones.
✅ Quick Check
A method has grown to six if/else branches, one per "type" of thing it handles.
What does the Open/Closed Principle suggest?
Next up: once you're using shared interfaces and subclasses, a new question shows up — can every subclass truly stand in for its parent? That's the Liskov Substitution Principle.