What is OCP?

The Open/Closed Principle is defined as follows:

Software entities should be open for extension, but closed for modification.

It may sound a bit “philosophical,” but in practice, it’s very down to earth.

  • Open (for extension): easy to add new features

  • Closed (for modification): no need to directly change existing code, reducing the risk of domino bugs

In other words:
The code you write today should be mentally prepared for new features tomorrow—without breaking what already works.

A Simple Analogy

Imagine you have a power outlet.

  • Tomorrow, if you want to plug in a laptop, you just plug it in.

  • If you need a 3-prong plug, you buy an adapter.

You don’t have to break the wall and rewire everything every time you buy a new device.

That’s exactly the spirit of OCP.

A Practical Code Example

Suppose you’re building a system to calculate areas of geometric shapes.

❌ Code that violates OCP

class AreaCalculator {
  calculate(shape: any) {
    if (shape.type === 'rectangle') {
      return shape.width * shape.height;
    }
    if (shape.type === 'circle') {
      return Math.PI * shape.radius * shape.radius;
    }
  }
}

const calc = new AreaCalculator();
console.log(calc.calculate({ type: 'rectangle', width: 10, height: 20 }));
console.log(calc.calculate({ type: 'circle', radius: 5 }));

Problems:

  • If tomorrow you want to add a Triangle, you must modify the calculate method.

  • Modifying existing code = risk of breaking something that was already stable.


✅ Improved Version Following OCP

interface Shape {
  getArea(): number;
}

class Rectangle implements Shape {
  constructor(private width: number, private height: number) {}
  getArea() {
    return this.width * this.height;
  }
}

class Circle implements Shape {
  constructor(private radius: number) {}
  getArea() {
    return Math.PI * this.radius * this.radius;
  }
}

class AreaCalculator {
  calculate(shape: Shape) {
    return shape.getArea();
  }
}

// Usage
const calc = new AreaCalculator();
console.log(calc.calculate(new Rectangle(10, 20)));
console.log(calc.calculate(new Circle(5)));

Now, if you want to add Triangle, you simply create a new class that implements Shape.
No existing code needs to be touched.

Real-World Usage in Frameworks

In NestJS

You can clearly see OCP in Dependency Injection.

  • Today you use MySQL

  • Tomorrow you switch to Postgres

You only need a new implementation—no changes to UserService.

interface Database {
  save(data: any): void;
}

@Injectable()
class MySQLDatabase implements Database {
  save(data: any) {
    console.log('Save to MySQL', data);
  }
}

@Injectable()
class UserService {
  constructor(private readonly db: Database) {}

  addUser(user: string) {
    this.db.save(user);
  }
}

The beauty here:
You extend behavior by adding new implementations, not by modifying existing services.

In React

OCP can also be applied to component rendering.
For example, a Button that supports multiple styles.

❌ Violating OCP

type ButtonType = 'primary' | 'secondary';

const Button: React.FC<{ type: ButtonType }> = ({ type, children }) => {
  if (type === 'primary') return <button className="bg-blue-500">{children}</button>;
  if (type === 'secondary') return <button className="bg-gray-500">{children}</button>;
  return null;
};

Problem:
To add a danger button, you must modify the component directly.


✅ Refactored with OCP

interface ButtonProps {
  render: (children: React.ReactNode) => JSX.Element;
  children: React.ReactNode;
}

const Button = ({ render, children }: ButtonProps) => render(children);

// Usage
<Button render={(c) => <button className="bg-blue-500">{c}</button>}>Save</Button>
<Button render={(c) => <button className="bg-red-500">{c}</button>}>Delete</Button>

Now you can extend endlessly without touching the Button component.

When Is OCP Useful?

  • When the system is likely to change frequently
    (e.g. new product types, payment methods, user roles)

  • When you want to reduce bug risks from modifying existing code

  • When working in a team—others can add features without breaking your code

A Small Warning

Don’t overuse OCP.

If you try to “prepare for every possible future,” your code can easily turn into over-engineering.

Golden Rule:

  • If extension is very likely → apply OCP

  • If you’re not sure → keep it simple first, refactor later

The Open/Closed Principle doesn’t force you to predict the future.
It simply helps your code adapt gracefully to change.

Remember:

Adding should be easy. Modifying should be rare.

And next time you find yourself digging through old code just to add a tiny feature, ask yourself:

“Did I apply OCP correctly?”