Open/Closed Principle
You'll learn to
- -Design classes that are open for extension but closed for modification, typically via polymorphism over conditionals
- -Recognize a growing if/else or switch chain as the signal that OCP is being violated
The Open/Closed Principle states that a class should be open for extension but closed for modification - you should be able to add new behavior without editing code that already works and is already tested. The mechanism that makes this possible is almost always polymorphism: instead of branching on type inside one method, define a common interface and let each type provide its own implementation.
The Growing Switch Statement Is the Tell
The most common OCP violation in an interview whiteboard is a method that branches on a type or enum, and where every new case requires editing that same method again. A discount calculator that starts with if type == "student"... and grows an elif for every new discount type is technically working code, but each new discount type means re-touching, re-testing, and re-risking a method that already worked for every previous case.
# Before: every new discount type means editing this method again
def calculate_discount(customer_type: str, price: float) -> float:
if customer_type == "student":
return price * 0.9
elif customer_type == "senior":
return price * 0.85
elif customer_type == "military": # <- added later, method edited again
return price * 0.8
return price
# After: closed for modification, open for extension
class DiscountStrategy(ABC):
@abstractmethod
def apply(self, price: float) -> float: ...
class StudentDiscount(DiscountStrategy):
def apply(self, price: float) -> float: return price * 0.9
class MilitaryDiscount(DiscountStrategy): # <- new file, zero existing code touched
def apply(self, price: float) -> float: return price * 0.8OCP Is Not "Never Change Anything"
This principle does not mean existing code is frozen forever - bug fixes and genuine requirement changes to existing behavior are normal and expected. OCP specifically targets the case of adding a new variant of existing behavior: a new discount type, a new payment method, a new notification channel. The goal is that "add a new kind of X" should mean writing a new class, not re-opening a method that every existing kind of X already depends on.
OCP and Strategy (a later chapter) are close cousins: OCP is the principle, Strategy is very often the concrete pattern used to satisfy it when the varying behavior is "which algorithm or rule applies here."
You see a NotificationSender class with a send(type, message) method containing if type == "email" / elif type == "sms" branches. How would you flag this?
"That's fine, it works and handles both cases correctly."
"It works today, but every new channel means editing this method again and re-risking the existing branches - that's an Open/Closed violation. I'd define a NotificationChannel interface with an EmailChannel and SmsChannel implementation, and have the sender iterate over a list of channels instead of branching on type. A new channel becomes a new class, not a new elif."
What does "open for extension, closed for modification" mean in practice?