Lesson 2 of 5

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.

← Back to
Next →