ReviseAlgo Logo

SOLID Principles

Open/Closed Principle

Open for extension, closed for modification

Last Updated: June 26, 2026 20 min read

The Open/Closed Principle (OCP) is the second of the SOLID design principles, introduced by Bertrand Meyer in 1988 and popularized by Robert C. Martin. It states that "software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification." OCP ensures that we can add new features without altering existing, thoroughly tested source code.

Open/Closed Principle

CLOSED for Modification
PaymentProcessor
processPayment(strategy: PaymentStrategy)
No changes needed when adding new payment types!
uses ▽
«interface»
PaymentStrategy
+ pay(amount): boolean
OPEN for Extension
CreditCard
pay() → card API
PayPal
pay() → PayPal API
UPI (new!)
pay() → UPI API
Add new payment types without modifying PaymentProcessor

1. Learning Objectives

  • Understand the formal definition of the Open/Closed Principle.
  • Identify code modifications that violate OCP (e.g., repeating if-else or switch-case structures).
  • Apply polymorphism, abstraction, and inheritance to build OCP-compliant classes.
  • Contrast "Before vs. After" UML structures to visualize polymorphism maps.
  • Implement plug-and-play code components in Java, Python, and C++.

2. Problem Statement

In traditional programming, when a new requirement is introduced, developers edit the existing code blocks directly.

For example, if you have an AreaCalculator class that calculates the area of circles and rectangles, adding support for a new shape (e.g. a Triangle) forces you to:

  • Modify the AreaCalculator source file.
  • Add another else-if statement inside the calculation loop.
  • Re-test the entire class to ensure circles and rectangles still calculate correctly, introducing regression risk.

If you must rewrite, recompile, and redeploy existing code every time a new feature is requested, software development speeds slow down and risk grows exponentially.

3. Real-world Analogy

Think of a Vacuum Cleaner and its Attachments:

  • The Rigged Vacuum (Violates OCP): A vacuum cleaner built with a single, unremovable floor nozzle. If you want to clean dust from curtains, you must crack open the vacuum casing, solder on a custom hose extension, and seal it back up. Modifying the motor assembly risks breaking the vacuum's primary suction.
  • The Plug-and-Play Vacuum (OCP Compliant): A vacuum cleaner built with a standard port. If you want to clean curtains, crevices, or pet hair, you simply snap on a specialized attachment (crevice tool, brush nozzle) to the port. The vacuum housing (the core) remains completely closed for modification, but the system's utility is open for extension.

4. Theory

The core of the Open/Closed Principle lies in two concepts:

  • Closed for Modification: The source code of existing, stable modules should not be modified once they are completed and tested.
  • Open for Extension: The system's behavior must be extendable. We should be able to make the class behave in new ways when requirements change.

This is achieved by separating the high-level policy from low-level details using abstractions (interfaces or abstract classes). High-level classes depend only on the abstraction. When we need a new behavior concretion, we write a new class implementing the interface, leaving the high-level orchestrator class untouched.

5. Visual Diagrams (Before vs. After OCP structures)

Before: Tight Coupling (Violates OCP)

Adding a new shape forces editing the calculateTotalArea method inside AreaCalculator:

AreaCalculator
+ calculateArea(Object shape)
if (shape instanceof Circle) ...
else if (shape instanceof Rectangle) ...

After: Polymorphism (OCP Compliant)

AreaCalculator depends on the Shape interface. New shapes can be added by implementing Shape without touching AreaCalculator:

AreaCalculator
+ calculateTotalArea(List<Shape>)
depends on
«interface» Shape
+ calculateArea(): double
Circle
+ calculateArea()
Rectangle
+ calculateArea()
Triangle (New!)
+ calculateArea()

6. Syntax Explanation

OCP uses polymorphic interfaces or base classes to define behavior contracts:

  • Java: Declare interfaces or abstract classes with abstract methods (e.g. double calculateArea()). Subclasses implement them using the implements keyword and the @Override annotation.
  • Python: Use Abstract Base Classes (ABCs) from the abc module and the @abstractmethod decorator to enforce contracts.
  • C++: Declare pure virtual methods (e.g., virtual double calculateArea() const = 0;) in abstract base classes. Inherit using public Shape and override virtual functions.

7. Step-by-Step Implementation

  • Step 1: Identify code segments containing nested conditional checks (e.g. if-else, switch-case, or type casting).
  • Step 2: Extract the target operations into a unified interface declaration (e.g. Shape with calculateArea()).
  • Step 3: Implement concrete subclasses for each operation type (e.g., Circle, Rectangle).
  • Step 4: Update the orchestrator class (e.g. AreaCalculator) to accept a list/collection of interface types and loop over them polymorphically.

8. Complete Code (Mini Project)

9. Code Walkthrough

In the OCP design above:

  • The AreaCalculator class is completely closed for modification. It contains no if-else type-checking blocks, nor does it reference concrete shapes directly.
  • If the product team requests support for a Pentagon or Hexagon, we do not edit AreaCalculator. We simply write a new class class Pentagon implements Shape. The compiler routes the call to the pentagon's calculateArea() implementation at runtime.

10. Execution Flow

  1. Instantiation: Client constructs a list of shapes, creating circles, rectangles, and triangles.
  2. Call: Client passes the list to calculator.calculateTotalArea(shapes).
  3. Polymorphic Dispatch: As the loop iterates, the runtime inspects the actual object header of each element in the heap and resolves the correct calculateArea() method address.

11. Internal Working

At the compiler and JVM level, OCP relies on Dynamic Dispatch and Virtual Method Tables (Vtables):

  • In C++, virtual functions add a hidden member pointer (vptr) to the object layout. The vptr points to a table of function pointers (the vtable) generated by the compiler. Calling shape->calculateArea() translates to a runtime lookup in the vtable to fetch the address of the actual subclass implementation (e.g. Circle::calculateArea()).
  • In Java, the JVM uses the invokevirtual bytecode instruction. The JVM checks the class type of the object reference at runtime and resolves the method signature dynamically, avoiding hardcoded branch instructions. This decoupling enables us to add classes dynamically at runtime using classloaders without recompiling the calling code.

12. Complexity Analysis

  • Time Complexity: $O(1)$ virtual function call overhead per invocation. Dynamic dispatch resolves in constant time.
  • Space Complexity: $O(M)$ where $M$ is the number of virtual methods in the class. A single vtable is shared by all instances of a class.

13. Best Practices

  • Depend on abstractions: Always declare variables, method parameters, and collection types as interface types rather than concrete subclasses.
  • Keep interfaces stable: The abstraction itself must remain stable. Modifying methods on an interface forces updates to all implementing subclasses, violating OCP.
  • Utilize design patterns: Use the Strategy, Factory, Decorator, or Template Method patterns to establish clean OCP extension points.

14. Common Mistakes

  • Over-abstracting too early: Creating complex interface maps for parts of the system that are simple and highly unlikely to change, violating the KISS and YAGNI principles.
  • Using type casting or downcasting: Writing if (shape instanceof Circle) inside a polymorphic method. This bypasses dynamic dispatch and creates a direct concretion check, violating OCP.
  • Changing interface contracts: Adding new methods to public interfaces without providing default implementations (in Java 8+) or abstract wrappers, breaking all client implementations.

15. Interview Questions

Q: How do you implement the Open/Closed Principle in a codebase?
Answer: Identify the parts of the code likely to change (e.g. payment methods, shape area calculations). Extract these behaviors into an interface contract. Implement concrete classes for each variation. Have the orchestrator class reference the interface rather than the concretions, allowing extension without modification.
Q: What is a vtable and how does it enable polymorphism?
Answer: A vtable (Virtual Method Table) is a compiler-generated table of function pointers for classes containing virtual functions. At runtime, the object's virtual table pointer (vptr) is dereferenced to look up the actual method address for dynamic dispatch.
Q: When is it acceptable to violate OCP?
Answer: When the system requirements are simple and the logic is highly unlikely to change. Forcing OCP using complex interfaces for a static, one-off utility class violates the KISS and YAGNI principles.

16. Practice Exercises

  • Easy: Refactor a NotificationService containing direct sendEmail() and sendSMS() calls using a polymorphic NotificationSender interface.
  • Medium: Design a tax calculation engine where tax rules differ by country. Implement it using OCP so that adding a new country's tax calculator does not require editing the base orchestrator.
  • Hard: Create a database query parser supporting multiple SQL dialects (MySQL, Postgres, Oracle). Design it so that adding a new dialect parser can be done by implementing an abstract class contract, and verify your design using dynamic plugin loading.

17. Challenge Problem

Design an e-commerce promotion discount service. The service must evaluate orders and apply discounts based on rules like: percentage off, flat discount, buy-one-get-one-free, and category-specific loyalty promotions. Design a modular OCP hierarchy that allows marketing managers to add new discount rules dynamically by registering new rule classes, and ensure that existing rule execution logic remains unchanged.

18. Summary

  • OCP states that software entities should be open for extension but closed for modification.
  • This principle is implemented using polymorphism, abstraction, and dynamic dispatch.
  • Using interfaces and abstract classes shields the core orchestrator from changes in low-level details.
  • Adding new features is done by writing new classes, leaving the existing tested codebase untouched.

19. Cheat Sheet

Principle Violation Symptom Actionable Design Pattern Primary Mechanism
SRP God Classes, mixing databases & UI layers Separate Entities, Repositories, Services Delegation and Cohesion partitioning
OCP if-else or switch-case blocks matching types Strategy, Factory, Decorator, Template Method Polymorphic Abstraction and Dynamic Dispatch

20. Quiz

1. What does the Open/Closed Principle state?

A) Open for inheritance, closed for interface implementation
B) Open for extension, closed for modification (Correct)
C) Open for database access, closed for network connections

2. Which concept is most critical to implementing OCP?

A) Global variables
B) Polymorphism and Abstraction (Correct)
C) Nested loops

3. What is a common sign that code violates OCP?

A) Using unit testing libraries
B) Frequent modifications of switch-case or if-else chains to support new types (Correct)
C) Declaring final variables

4. Why is modifying existing tested source code risky?

A) It slows down compiler compilation times
B) It introduces regression risks, potentially breaking existing functionality (Correct)
C) It causes memory leaks

5. In C++, what mechanism resolves method calls at runtime in polymorphic hierarchies?

A) Stack trace indexing
B) Virtual Method Table (vtable) pointer resolution (Correct)
C) Preprocessor macros

6. What is the JIT bytecode instruction used in Java for virtual method dispatch?

A) invokevirtual (Correct)
B) invokestatic
C) invokespecial

7. What is a key design pattern commonly used to achieve OCP?

A) Singleton Pattern
B) Strategy Pattern (Correct)
C) Prototype Pattern

8. Why is downcasting (using instanceof or dynamic_cast) inside a polymorphic method bad under OCP?

A) It defeats polymorphism by coupling the orchestrator to concrete classes (Correct)
B) It requires additional class loading memory
C) It causes division by zero

9. What is a common mistake when applying OCP?

A) Creating abstractions for code that is simple and unlikely to change, violating KISS/YAGNI (Correct)
B) Using final class decorators in Python
C) Separating concerns too far

10. What does the term "stable abstraction" refer to under OCP?

A) An interface that is rarely changed or updated once compiled, shielding clients from concrete details (Correct)
B) A class that cannot have subclasses
C) A static data structure

21. Next Lesson Preview

In the next lesson, we will explore the Liskov Substitution Principle (LSP) to learn how to design derived subclasses that can seamlessly stand in for their parent classes without breaking system execution!