Applying the Open/Closed Principle in TypeScript

November, 22nd 2024 2 min read

Expanding existing functionality in software can quickly lead to rigid, fragile codebases if the system isn’t designed for extension. The Open/Closed Principle (OCP)—one of the core SOLID principles—aims to solve this problem. It states that:

  • Modules should be open for extension (you can introduce new behavior)
  • But closed for modification (existing code should stay untouched)

In this guide, you’ll learn what the principle means in practice, why it matters, and how to implement it cleanly in TypeScript using the Strategy Pattern.

What Is the Open/Closed Principle?

The OCP, formulated by Bertrand Meyer, ensures that software can evolve without constantly rewriting stable, tested code.

Why this matters:

  1. Scalability — new features don’t break old ones.
  2. Testability — logic is split into small, isolated units.
  3. Maintainability — fewer code changes mean fewer bugs.
  4. Flexibility — behavior is controlled by interchangeable components.

A Bad Example: Violating the Open/Closed Principle

Let’s start with an example that doesn’t follow OCP:

ts
function calculate(a: number, b: number, op: string): number {
  switch (op) {
    case 'add':
      return a + b;
    case 'subtract':
      return a - b;
    default:
      throw new Error('Unsupported operation');
  }
}

This requires modifying the function whenever new operations are added — violating OCP.

Applying the Open/Closed Principle with the Strategy Pattern

1. Strategy Interface

ts
interface CalculationStrategy {
  execute(a: number, b: number): number;
}

2. Concrete Strategies

ts
class AddStrategy implements CalculationStrategy {
  execute(a: number, b: number): number {
    return a + b;
  }
}

class SubtractStrategy implements CalculationStrategy {
  execute(a: number, b: number): number {
    return a - b;
  }
}

3. Calculator Class

ts
class Calculator {
  constructor(private strategy: CalculationStrategy) {}

  setStrategy(strategy: CalculationStrategy) {
    this.strategy = strategy;
  }

  calculate(a: number, b: number): number {
    return this.strategy.execute(a, b);
  }
}

4. Adding New Behavior Without Modifying Existing Code

ts
class MultiplyStrategy implements CalculationStrategy {
  execute(a: number, b: number): number {
    return a * b;
  }
}

Usage Example

ts
const calculator = new Calculator(new AddStrategy());
calculator.calculate(6, 2); // 8

calculator.setStrategy(new SubtractStrategy());
calculator.calculate(6, 2); // 4

calculator.setStrategy(new MultiplyStrategy());
calculator.calculate(6, 2); // 12

Benefits

  • Extensibility without modifying existing logic
  • Cleaner architecture
  • Reusable strategy modules
  • Safer scaling

Conclusion

The Open/Closed Principle helps create scalable and maintainable TypeScript applications. Using the Strategy Pattern ensures new features can be introduced safely without touching proven, existing code.