← Home
🏗Crack System Design with DSA AnimatorSystem DesignNEW
🏗 System Design›SOLID Principles

🔓Open/Closed Principle (OCP)

Open for extension, closed for modification — add new behaviour without changing stable code.

📌 Key IdeaYou should be able to add new features by adding new code (new classes, new strategies), not by editing a growing switch or if-chain in a class everyone depends on.
✨

Why It Matters

  • Fewer regressions — Old behaviour stays untouched when you add the Nth variant.
  • Parallel work — Team A adds CryptoDiscountPolicy while Team B adds PartnerDiscountPolicy — no merge fight in one god method.
  • Test stability — Existing tests for OrderPricer stay green; you only add tests for the new policy.
  • Library mindset — Framework code stays small; product-specific rules live in extensions.
💡

In Layman's Terms

Think of a power strip with sockets. When you buy a new appliance you plug in an adapter — you do not cut open the strip and solder wires every time.

OCP asks your codebase for the same idea: the socket (abstraction) stays fixed; each plug (implementation) is its own thing.

🌍

Real-World Analogy

🎛 Plug-in architecture

Browsers don't recompile when someone ships a new video codec — they load a plugin or delegate to an implementation behind a stable API.

Your discount engine, notification sender, or payment gateway should work the same way: stable orchestration code + swappable policies.

🖼

Visual

❌ Closed for extension
Every new discount tier edits the same method — merge conflicts and regressions.
  discountFor(Customer c) {
      if (c.isVip())        return 0.20;   // +new tier? edit here
      else if (c.isNew())  return 0.10;   // +wholesale? edit here
      else                 return 0.0;   // risk: merge conflicts, regressions
  }
✅ Open for extension
New rule = new class implementing the same interface — core pricer stays untouched.
  interface DiscountPolicy { BigDecimal rate(Customer c); }
  // New VIP tier?   → new VipDiscountPolicy implements DiscountPolicy
  // New wholesale?  → new WholesalePolicy implements DiscountPolicy
  // Core pricer     → never changes when you add a policy

Left: every new business rule edits one fragile method. Right: new rules ship as new classes behind one interface.

✅

When to Use

  • You expect many variants of the same decision (payment methods, notification channels, pricing rules).
  • The core flow is stable but business asks for new types every sprint.
  • You already see `switch (type)` or long `if / else if` chains growing in one class.
  • You publish an API or SDK — callers extend via plugins, not by patching your source.
🚫

When NOT to Use

  • Only one implementation exists and will stay that way — YAGNI; a simple class is enough.
  • The behaviour is truly one-off — avoid abstract factories for a single if.
  • Premature abstraction: extract an interface after the second duplication (Rule of Three).
🚨

Anti-Pattern (What NOT to Do)

Growing switch — every new channel edits this methodBadjava
public final class NotificationSender {

    public void send(User u, String channel, String body) {
        switch (channel) {
            case "email" -> smtpSend(u.getEmail(), body);
            case "sms"   -> twilioSend(u.getPhone(), body);
            case "push"  -> fcmSend(u.getDeviceToken(), body);
            // Adding Slack? WhatsApp? Edit here — OCP violation.
            default -> throw new IllegalArgumentException(channel);
        }
    }

    private void smtpSend(String to, String msg) { /* ... */ }
    private void twilioSend(String phone, String msg) { /* ... */ }
    private void fcmSend(String token, String msg) { /* ... */ }
}
Each new channel forces a change to this class and re-testing every path.
✅

The Right Way

Strategy — add a channel by adding a classGoodjava
public interface NotificationChannel {
    boolean supports(String channelId);
    void send(User u, String body);
}

public final class NotificationSender {
    private final List<NotificationChannel> channels;

    public NotificationSender(List<NotificationChannel> channels) {
        this.channels = List.copyOf(channels);
    }

    public void send(User u, String channelId, String body) {
        channels.stream()
                .filter(c -> c.supports(channelId))
                .findFirst()
                .orElseThrow(() -> new IllegalArgumentException(channelId))
                .send(u, body);
    }
}

// New Slack channel = new class SlackChannel implements NotificationChannel — zero edits above.
Core NotificationSender stays stable; new behaviour ships as new implementations.
⚖️

Pros & Cons

👍 Pros
  • Stable core — fewer accidental breaks when extending.
  • Maps cleanly to Strategy, Template Method, Decorator, Chain of Responsibility.
  • Easier to reason about what changed in code review — one new file vs one messy diff.
👎 Cons
  • More types and indirection — jumping between classes to read the full story.
  • Wrong abstraction traps you — if the interface doesn't fit the next variant, you refactor.
  • Team discipline needed — resist stuffing new logic back into the switch "just this once".
📊

Switch statement vs Strategy (OCP lens)

AspectBig switch / if-chainStrategy + registry
Add new variantEdit central methodAdd new class
Risk to old pathsRegression on every editOld code untouched
Merge conflictsEveryone edits same methodDifferent files
TestingMust retest all branchesTest only new strategy
When it's OK2–3 fixed cases foreverMany variants or frequent additions
🔀

Common Confusions Cleared Up

OCP vs YAGNI

OCP says design extension points where change is likely. YAGNI says don't build those points until you need them. Use Rule of Three: after the second copy-paste or second branch in the switch, extract the abstraction.

OCP vs SRP

SRP splits reasons to change across classes. OCP keeps existing classes closed while you extend elsewhere. They work together: SRP gives you small units; OCP keeps those units from turning into edit magnets.

⚠️

Common Mistakes / Anti-Patterns

  • "Temporary" if that lives for five years.
  • Abstract Processor with one implementation — extract when the second appears.
  • Copy-paste entire methods instead of extracting a shared abstraction.
🎤

Interview Q&A

🔮

Likely Follow-up Questions

  • When would you prefer inheritance vs composition for OCP?
  • How does Spring's `@ConditionalOnProperty` relate to extension?
  • What breaks OCP in a microservice? (shared library everyone edits for each feature)
  • Explain Template Method vs Strategy for the same pricing flow.
🧾

Quick Cheat Sheet

One-linerExtend behaviour with new types, not edits to the core.
Smellswitch (kind) / if (type) growing every sprint.
FixStrategy · Template Method · Decorator · plugin registry.
BalanceYAGNI until the second variant; then extract.
🔗

Related Topics