Menu

Earn Premium with Referrals

Invite your friends and earn Premium rewards through our referral program.

See how it works and start inviting friends.

Open-Closed Principle
LLD

Open-Closed Principle

Understand how software should be open for extension while remaining closed for modification.

Open-Closed Principle (OCP)

The Problem It Solves

Discount calculation for a store: festival season adds FESTIVAL10, then BULK5, then loyalty tiers, then coupons. The implementation is a switch statement inside the tested, deployed checkout path. Every new discount edits working code — re-review, re-test, re-deploy the whole flow, and one bad merge in the switch breaks checkout on Black Friday. OCP (Bertrand Meyer): entities should be open for extension, closed for modification — new behavior arrives as new code, not as edits to proven code.

Before / After

 BEFORE: variation inside core          AFTER: core closed, variants added

 ┌────────────────────────────┐         ┌───────────────┐
 │ Checkout                   │         │ Checkout      │  frozen:
 │ if (FESTIVAL10) ...        │         │ + Discount    │  depends on interface
 │ else if (BULK5) ...        │ ──────► │   interface   │  only
 │ else if (LOYALTY) ...      │         └──────┬────────┘
 │ else ...   ← edit per sale │                ▼
 └────────────────────────────┘        Festival | Bulk | Loyalty | Coupon
     every addition = risky edit       each variant = NEW file, zero edits upstream

Mechanics

interface Discount { Money apply(Money subtotal, Cart cart); }

class Checkout {
    private final Discount discount;                 // injected policy
    Checkout(Discount d) { this.discount = d; }
    Money total(Cart cart) {
        Money sub = cart.subtotal();
        return sub.minus(discount.apply(sub, cart)); // never edited again
    }
}

class FestivalDiscount implements Discount { ... }   // sale season: add file,
class CouponDiscount implements Discount { ... }     // wire in config. Done.

Checkout has been closed since the extraction; growth is purely additive.

What Counts as Extension

MechanismClosed-core effect
Strategy/interface implementationsNew behavior = new class
Template-method hooksSubclass overrides hook points
Decorator wrappingAdd responsibilities without touching wrapped code
Parameterization/config valuesBehavior varies by data, not edits

OCP does not mandate interfaces everywhere — a lookup table or config value can close an axis too.

The Cost Side

  • Guessing the wrong axis produces abstraction sprawl: five strategies where two branches would do.
  • Every seam costs reading effort and indirection.
  • Rule: apply OCP to axes with demonstrated or documented change frequency (“discount rules change monthly” justifies the interface).

Interview Framing

  • Precise definition matters: “closed” means no edits to existing tested units when adding behavior — not that files never change ever again (refactoring still happens).
  • Strong candidates connect OCP to encapsulate-what-varies: identifying the axis is the prerequisite; the strategy interface is the enforcement.

Failure Modes

  • Switch-statement sprawl across many classes (adding a discount means editing N switches) — solved by polymorphism or registry maps replacing conditionals.
  • Abstraction built before the second real variant exists — YAGNI applies until change is evidenced.

The Design Question Behind OCP

“Closed for modification” requires knowing where to leave the opening. Extension points are deliberate, named places where future behavior plugs in. Design them wrong and you get either rigid code (no seam where change came) or Swiss-army abstractions (seams everywhere, none used). This page is about choosing seam locations and mechanisms deliberately.

Predicting Variation

 Requirements text                Likely axis            Mechanism
 ─────────────────────────       ─────────────────      ────────────────
 "card, UPI, wallets..."         payment algorithms     Strategy
 "later: Slack, SMS alerts"      notification channels  Observer/Strategy
 "admin can add new roles"       authorization rules    Policy objects / RBAC data
 "report as PDF and Excel"       output formats         Template method
 "pricing differs per region"    configuration values   Data-driven lookup (no classes)

The last row matters most in practice: many axes are data, not behavior — a table of region→tax-rate beats a TaxStrategy class hierarchy. OCP is satisfied by parameterization; classes are only needed when logic varies.

Mechanism Selection

MechanismBest whenCost
Strategy interfaceAlgorithm swaps, runtime selectionClient must know variants
Template-method hookFixed skeleton, varying stepsInherits inheritance coupling
DecoratorStacking cross-cutting add-onsWrapper sprawl if unbounded
Plugin registry (map of impls)Third parties adding variantsDiscovery/lifecycle machinery
Config/data-driven valuesVarying numbers, thresholds, flagsNone — cheapest seam
Event/callback hooksUnknown future listeners need notificationOrdering/debugging complexity

Real-World Seams

  • Servlet filters / Spring interceptors: framework core frozen since 2000s; every cross-cutting concern ever added arrived as an inserted filter — OCP at platform scale.
  • JDBC: applications never edit driver code; vendors ship implementations of stable interfaces.
  • Java streams collectors: Collectors.toList() etc. — the reduction skeleton closed, collectors extensible.

Designing the Seam Well

  1. Name it after the axis (DiscountPolicy, not Processor2).
  2. Keep the contract minimal (ISP) — fat extension interfaces freeze wrongly.
  3. Document lifecycle expectations (thread-safety, call frequency) on the interface — plugin authors cannot read your mind.
  4. Version the contract: adding a default method is backward-compatible evolution; changing signatures breaks every third-party plugin.

Interview Framing

  • “Where would you expect this system to grow?” answered from requirement wording (future-tense phrases, alternative lists) demonstrates design foresight.
  • The senior qualifier: proposing the cheapest sufficient seam — config value before class hierarchy — shows OCP judgment rather than pattern reflexes.

Failure Modes

  • Extension points nobody extends (speculative generality) — remove after evidence they’re dead.
  • Seams leaking internal state to plugins (passing mutable internals) — plugins couple to representation; hand them copies or narrow views.

My Private Notes

Notes are auto-saved locally to this device.