HT
How Things Work
System Online

SOLID Principles

The five object-oriented design principles that keep code maintainable — each one a different way to isolate change. Spot the violation, then see the refactor.

How It Works

SOLID is a set of five principles (coined by Robert C. Martin) for writing object-oriented code that's easy to change, test, and extend. They aren't rules to apply mechanically — they're heuristics that all push toward the same goal: high cohesion and low coupling, so a change in one place doesn't cascade into many.

1
S — Single Responsibility

A class should have one reason to change. Bundling data, formatting, persistence, and delivery into one class means a change to any of them risks all of them. Split responsibilities so each unit has a single, well-defined job.

2
O — Open/Closed

Software entities should be open for extension but closed for modification. Instead of editing a growing if/else (or switch) every time a new case appears, depend on an abstraction so new behavior is added as a new class — leaving tested code untouched.

3
L — Liskov Substitution

A subtype must be substitutable for its base type without breaking callers. If overriding a method violates the base class's contract (the classic Square/Rectangle trap), inheritance is the wrong tool — model a shared abstraction instead.

4
I & D — Segregation and Inversion

Interface Segregation: prefer many small, role-specific interfaces over one fat one, so clients don't depend on methods they ignore. Dependency Inversion: high-level modules and low-level details should both depend on abstractions, with the concrete detail injected — the foundation of testable, swappable code.

Key Concepts

🎯Single Responsibility

One class, one reason to change. Isolate unrelated jobs so a change to one can't break another.

🔓Open/Closed

Extend behavior by adding new code (new classes), not by modifying existing, tested code.

🔁Liskov Substitution

Subtypes must honor the base type's contract so they're safely interchangeable. No surprising overrides.

✂️Interface Segregation

Many focused interfaces beat one fat interface; clients depend only on what they actually use.

🔌Dependency Inversion

Depend on abstractions, not concretions, and inject the concrete detail. Enables testing and swapping.

🧲Cohesion vs Coupling

SOLID raises cohesion (related things together) and lowers coupling (fewer rigid dependencies) — the real goal.

Dependency Inversion in practice
tsx
1// The Dependency Inversion Principle in one picture.
2
3// ❌ High-level policy hard-wired to a low-level detail
4class OrderService {
5 private db = new MySqlDatabase(); // concrete dependency
6 place(order) { this.db.insert(order); } // untestable, unswappable
7}
8
9// ✅ Both depend on an abstraction; the detail is injected
10interface OrderStore { save(o: Order): Promise<void>; }
11
12class OrderService {
13 constructor(private store: OrderStore) {} // inversion of control
14 place(order) { return this.store.save(order); }
15}
16
17class SqlOrderStore implements OrderStore { /* prod */ }
18class InMemoryOrderStore implements OrderStore { /* tests */ }
19
20// Wiring happens at the composition root (DI container):
21const service = new OrderService(new SqlOrderStore());
💡
Why This Matters

SOLID is the shared vocabulary of professional OO design and a near-guaranteed interview topic. More practically, codebases that respect these principles are dramatically cheaper to change: new features slot in as new classes, dependencies are swappable, and unit tests are easy because seams exist where they need to.

Common Pitfalls

Over-applying SOLID — splitting everything into tiny classes and interfaces creates needless indirection (an anti-pattern of its own).
Treating Single Responsibility as 'one method per class' rather than 'one reason to change'.
Using inheritance where it breaks the base contract (Liskov violation) instead of composition.
Adding an interface with exactly one implementation 'for SOLID' when there's no real need to vary it.
Confusing Dependency Inversion (depend on abstractions) with Dependency Injection (the technique that delivers them).
Real-World Use Cases

1The Payment Switch That Grew Out of Control

Scenario

A checkout has a processPayment() method with a switch over 'stripe', 'paypal', 'applepay'. Adding a new provider means editing this method, re-testing every branch, and risking a regression in unrelated providers.

Problem

The method violates Open/Closed: every new payment type modifies existing, tested code. The switch also tends to accumulate provider-specific quirks, turning into a fragile God method.

Solution

Define a PaymentProvider interface with a single charge() method. Each provider becomes its own class implementing it, selected via a factory. Adding a provider is now a new class plus one registration line — the checkout code never changes.

💡

Takeaway: When you find yourself editing the same switch/if-else for every new case, apply Open/Closed: invert to an interface and add behavior as new types. Existing code stays closed and safe.

2Untestable Service Pinned to a Real Database

Scenario

A team can't unit-test their OrderService because it news up a real database connection inside the constructor. Tests are slow, flaky, and require a live DB.

Problem

OrderService violates Dependency Inversion — it depends on a concrete MySqlDatabase. There's no seam to substitute a fake, so 'unit' tests are really integration tests against infrastructure.

Solution

Introduce an OrderStore interface, inject it via the constructor, and wire the concrete SqlOrderStore at the composition root (DI container). Tests pass an InMemoryOrderStore — fast, deterministic, no infrastructure.

💡

Takeaway: Dependency Inversion isn't academic — it's what makes code testable. Inject abstractions so you can substitute fakes, and let a DI container assemble the real implementations in production.

Join the discussion

Comments

?

Loading comments...

Cookie preferences

We use essential cookies for sign-in and preferences. With your permission, we also use basic analytics to improve lessons.