# SOLID Principles — Five Rules for Clean Object-Oriented Code

Source: https://skills101.in/learn/oops/solid-principles — Skills101 free tutorial (OOP Concepts). Updated 2026-11-09.

**SOLID** is a set of five design rules that keep object-oriented code easy to **change** and **test**. They're asked in interviews for experienced roles and increasingly for freshers too.

![SOLID: S — single responsibility; O — open/closed; L — Liskov substitution; I — interface segregation; D — dependency inversion](https://skills101.in/learn-img/oops/solid.svg)

## S — Single Responsibility Principle

> A class should have **one reason to change**.

❌ One `Invoice` class that calculates totals, saves to the database **and** sends emails. A change to email wording forces you to touch billing code.

✅ Split it:

```python
class Invoice:
    def __init__(self, items): self.items = items
    def total(self): return sum(price * qty for price, qty in self.items)

class InvoiceRepository:
    def save(self, invoice): return f"saved invoice of {invoice.total()}"

class InvoiceMailer:
    def send(self, invoice, to): return f"mailed {invoice.total()} to {to}"

inv = Invoice([(499, 2), (99, 1)])
print(inv.total(), "|", InvoiceRepository().save(inv), "|", InvoiceMailer().send(inv, "asha@x.in"))
```

```text
1097 | saved invoice of 1097 | mailed 1097 to asha@x.in
```

## O — Open/Closed Principle

> Classes should be **open for extension, closed for modification**.

❌ Every new discount type means editing an `if … elif …` chain inside `apply_discount()`.

✅ Add a new class instead:

```python
class Discount:
    def apply(self, price): return price

class Festive(Discount):
    def apply(self, price): return price * 0.8

class Student(Discount):
    def apply(self, price): return price - 200

def checkout(price, discount: Discount):
    return discount.apply(price)

print(checkout(2000, Festive()), checkout(2000, Student()), checkout(2000, Discount()))
```

```text
1600.0 1800 2000
```

A new `Diwali` discount is a new class — `checkout` never changes.

## L — Liskov Substitution Principle

> A child class must work **anywhere** its parent works, without surprises.

Classic violation: `Square extends Rectangle`. Code that sets width = 5 and height = 4 expects area 20, but a Square forces both sides equal and breaks it. If a subclass needs to **refuse** or **change the meaning** of a parent method, it shouldn't be a subclass.

**Signs of violation:** a subclass throws "not supported" for an inherited method, or callers need `if isinstance(x, Child)` checks.

## I — Interface Segregation Principle

> Many small, focused interfaces are better than one big one.

❌ An interface `Machine` with `print()`, `scan()`, `fax()` forces a simple printer to implement `scan()` and `fax()` with errors.

✅ Separate `Printer`, `Scanner`, `Fax` interfaces; an all-in-one implements all three, a basic printer implements one.

## D — Dependency Inversion Principle

> Depend on **abstractions**, not concrete classes.

❌ `OrderService` creates `MySQLDatabase()` inside itself → you can't test it without a real database or switch to PostgreSQL easily.

✅ Pass the dependency in (dependency injection):

```python
class Store:                        # abstraction
    def save(self, order): raise NotImplementedError

class MySQLStore(Store):
    def save(self, order): return f"MySQL: saved {order}"

class FakeStore(Store):             # for tests — no database needed
    def save(self, order): return f"test: pretended to save {order}"

class OrderService:
    def __init__(self, store: Store):   # injected
        self.store = store
    def place(self, order):
        return self.store.save(order)

print(OrderService(MySQLStore()).place("order#42"))
print(OrderService(FakeStore()).place("order#42"))
```

```text
MySQL: saved order#42
test: pretended to save order#42
```

Frameworks like **Spring** (Java) do this injection for you.

## Common mistakes

- Applying SOLID mechanically to tiny scripts — it's for code that keeps growing.
- Confusing **Dependency Inversion** (principle) with **Dependency Injection** (a technique to achieve it).

## Quick recap

- **S** one reason to change · **O** extend, don't edit · **L** subclasses must be substitutable · **I** small interfaces · **D** depend on abstractions.
- Goal: change one thing without breaking others, and test easily.


Practice quiz and free certificate: https://skills101.in/learn/oops/solid-principles