Ever looked at code and thought, "What spaghetti monster wrote this?" Plot twist: it was you, 6 months ago.

Don't worry, we've all been there. That's exactly why SOLID principles exist — to save us from our past selves.

SOLID Principles Overview

What the Heck is SOLID?

SOLID is an acronym coined by Robert C. Martin (aka "Uncle Bob" — yes, that's his actual nickname in the dev world). It represents five principles that help you write code that doesn't make future-you want to cry.

Letter Principle The Gist
S Single Responsibility Do ONE thing well
O Open/Closed Add features without breaking stuff
L Liskov Substitution Kids should behave like their parents
I Interface Segregation Don't force-feed unnecessary features
D Dependency Inversion Depend on promises, not specifics

Let's break these down with real-world analogies that actually make sense.


S — Single Responsibility Principle

"A class should have only one reason to change."

The Restaurant Analogy

Imagine a restaurant where one person is the chef, waiter, cashier, dishwasher, AND accountant.

Sounds efficient? Nope. It's chaos.

  • Chef burns food while taking orders
  • Bills get mixed up while washing dishes
  • The health inspector is NOT happy

Single Responsibility

Good restaurants have specialists:

  • Chef → cooks food
  • Waiter → serves customers
  • Cashier → handles payments

Same with code. Each class should have ONE job.

Bad Code (The One-Man Band)

class SuperEmployee {
  cookFood() { /* ... */ }
  serveCustomers() { /* ... */ }
  calculateBills() { /* ... */ }
  washDishes() { /* ... */ }
  doTaxes() { /* ... */ }
}
// This poor class needs therapy

Good Code (The Dream Team)

class Chef {
  cookFood() { /* ... */ }
}

class Waiter {
  serveCustomers() { /* ... */ }
}

class Cashier {
  processPayment() { /* ... */ }
}
// Each class: "I know my job. I do it well."
**Quick Test:** Can you describe what your class does WITHOUT using the word "and"? If not, it's doing too much.

O — Open/Closed Principle

"Open for extension, closed for modification."

The Smartphone Analogy

Your iPhone doesn't require Apple to surgically open it every time you want a new app. You just... download the app.

The phone is closed (you can't modify iOS core) but open (you can extend it with apps).

Open Closed Principle

Bad Code (Surgery Required)

class PaymentProcessor {
  process(type: string, amount: number) {
    if (type === 'credit') {
      // credit card logic
    } else if (type === 'paypal') {
      // paypal logic
    } else if (type === 'crypto') {
      // crypto logic
    }
    // Adding Apple Pay? Time to crack open this class again...
  }
}

Every new payment method = modifying existing code = potential bugs = sad developers.

Good Code (Plugin Architecture)

interface PaymentMethod {
  pay(amount: number): void;
}

class CreditCard implements PaymentMethod {
  pay(amount: number) { /* credit card magic */ }
}

class PayPal implements PaymentMethod {
  pay(amount: number) { /* paypal wizardry */ }
}

// New payment method? Just add a new class!
class ApplePay implements PaymentMethod {
  pay(amount: number) { /* apple sorcery */ }
}
// Original code: untouched and unbothered
**Think of it like LEGO:** You add new pieces without melting the existing ones.

L — Liskov Substitution Principle

"Subclasses should be substitutable for their parent classes."

The Duck Analogy

If it looks like a duck, quacks like a duck, but needs batteries — you have a violation.

Liskov Substitution

A child class should be able to do everything its parent can do. No surprises, no exceptions, no "well, actually..."

The Classic Mistake: Square vs Rectangle

Mathematically, a square IS a rectangle. In code? It's a trap.

class Rectangle {
  setWidth(w: number) { this.width = w; }
  setHeight(h: number) { this.height = h; }
}

class Square extends Rectangle {
  // Uh oh... setting width MUST also set height
  setWidth(w: number) {
    this.width = w;
    this.height = w; // SURPRISE!
  }
}

Someone using a Rectangle expects setWidth to ONLY change width. The Square breaks that expectation.

The Fix: Don't Force It

interface Shape {
  getArea(): number;
}

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

class Square implements Shape {
  constructor(private side: number) {}
  getArea() { return this.side * this.side; }
}
// No inheritance drama. Just shapes doing shape things.
**Rule of Thumb:** If your subclass needs to throw errors for inherited methods, you're doing inheritance wrong.

I — Interface Segregation Principle

"Don't force classes to implement methods they don't use."

The Swiss Army Knife Problem

A Swiss Army Knife is cool for camping. But imagine forcing a surgeon to use one for operations.

"Sorry, I can't find the scalpel between the corkscrew and the toothpick."

Interface Segregation

Bad Code (The Monster Interface)

interface Worker {
  work(): void;
  eat(): void;
  sleep(): void;
  attendMeeting(): void;
  writeCode(): void;
  managePeople(): void;
}

class Robot implements Worker {
  work() { /* beep boop */ }
  eat() { throw new Error("I'm a robot!"); }
  sleep() { throw new Error("I don't sleep!"); }
  // ... more exceptions
}
// Robot is having an existential crisis

Good Code (Focused Interfaces)

interface Workable {
  work(): void;
}

interface Eatable {
  eat(): void;
}

interface Manageable {
  managePeople(): void;
}

class Human implements Workable, Eatable {
  work() { /* typing sounds */ }
  eat() { /* nom nom */ }
}

class Robot implements Workable {
  work() { /* beep boop */ }
  // No identity crisis here!
}
**Think Buffet, Not Set Menu:** Let classes pick what they need instead of forcing a fixed combo.

D — Dependency Inversion Principle

"Depend on abstractions, not concretions."

The Power Outlet Analogy

Your laptop charger doesn't care if the electricity comes from solar panels, wind turbines, or a hamster running on a wheel.

It just needs something that provides electricity through a standard outlet.

Dependency Inversion

Bad Code (Hardwired)

class OrderService {
  private db = new MySQLDatabase(); // Married to MySQL forever
  private email = new SendGrid();   // Tied to SendGrid

  createOrder() {
    this.db.save(order);      // What if we switch to PostgreSQL?
    this.email.send(receipt); // What if SendGrid raises prices?
  }
}
// Changing anything = rewriting everything

Good Code (Pluggable)

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

interface EmailService {
  send(to: string, message: string): void;
}

class OrderService {
  constructor(
    private db: Database,        // Any database works!
    private email: EmailService  // Any email service works!
  ) {}

  createOrder() {
    this.db.save(order);
    this.email.send(receipt);
  }
}

// Switching databases? Just pass a different implementation
const service = new OrderService(
  new PostgreSQL(),  // or MongoDB, or SQLite...
  new Mailchimp()    // or AWS SES, or smoke signals...
);
**It's like USB:** Your computer doesn't care what's plugged in — keyboard, mouse, or a USB-powered rocket launcher. It just knows how to talk to USB devices.

SOLID in 30 Seconds

Principle One-Liner Real World
Single Responsibility One job per class Chef cooks, waiter serves
Open/Closed Extend, don't modify Apps on your phone
Liskov Substitution Kids behave like parents A duck that needs batteries isn't a duck
Interface Segregation Small, focused interfaces Buffet > set menu
Dependency Inversion Depend on abstractions Power outlets, not specific power plants

When NOT to Over-Engineer

SOLID is great, but don't go crazy:

  • Writing a one-off script? Skip the ceremony.
  • Building a prototype? Get it working first.
  • Simple CRUD app? Don't architect it like Netflix.
**Remember:** The goal is maintainable code, not a PhD thesis. If you're creating 47 interfaces for a todo app, take a coffee break.

Wrapping Up

SOLID principles aren't commandments carved in stone. They're guidelines that help you write code that:

  • Your teammates can understand
  • Future-you won't curse at
  • Actually works when requirements change (and they always do)

Start with awareness. Notice when code feels off. That gut feeling? It's usually one of these principles being violated.

Now go forth and write code that doesn't suck.


Resources


Need help cleaning up your codebase? Contact CODERCOPS — we promise not to judge your spaghetti code.

Comments