> ## Documentation Index
> Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Interface Segregation Principle

> Many small interfaces beat one giant interface - don't force unnecessary stuff!

## 🧩 The ISP Rule

> **"Clients should not be forced to depend on methods they don't use."**

Imagine a **Swiss Army knife** 🔪 vs **specialized tools**:

* Swiss Army knife: Has everything, but each tool is mediocre
* Specialized tools: Each does one thing perfectly

ISP says: **Don't give a fish a bicycle interface!** 🐟🚲

<Tip>
  **Simple Rule**: Split big interfaces into small, focused ones. Classes only implement what they need!
</Tip>

**The gym membership analogy:** Imagine a gym that forces every member to sign up for swimming, weightlifting, yoga, boxing, AND rock climbing -- even if you only want to lift weights. You are paying for (and contractually obligated to maintain eligibility for) services you never use. ISP says the gym should offer separate memberships: a weights pass, a swim pass, a yoga pass. You pick only what you need. In code, this means small, focused interfaces so that implementing classes are not burdened with methods that are irrelevant to them.

***

## 🚨 The Problem: Fat Interfaces

### ❌ BAD: One Giant Interface

```python theme={null}
from abc import ABC, abstractmethod

class Worker(ABC):
    """Every worker must do ALL of these... 😰"""
    
    @abstractmethod
    def work(self):
        pass
    
    @abstractmethod
    def eat_lunch(self):
        pass
    
    @abstractmethod
    def attend_meeting(self):
        pass
    
    @abstractmethod
    def code(self):
        pass
    
    @abstractmethod
    def design(self):
        pass
    
    @abstractmethod
    def manage_team(self):
        pass

class Developer(Worker):
    def work(self):
        print("Writing code...")
    
    def eat_lunch(self):
        print("Eating pizza...")
    
    def attend_meeting(self):
        print("In standup...")
    
    def code(self):
        print("Coding Python...")
    
    def design(self):
        # 😰 Developer doesn't design UI!
        raise NotImplementedError("I'm not a designer!")
    
    def manage_team(self):
        # 😰 Developer doesn't manage!
        raise NotImplementedError("I'm not a manager!")

class Robot(Worker):
    def work(self):
        print("Processing...")
    
    def eat_lunch(self):
        # 😰 Robots don't eat!
        raise NotImplementedError("I don't eat!")
    
    def attend_meeting(self):
        # 😰 Robots don't attend meetings!
        raise NotImplementedError("I don't attend meetings!")
    
    def code(self):
        print("Auto-generating code...")
    
    def design(self):
        raise NotImplementedError("I don't design!")
    
    def manage_team(self):
        raise NotImplementedError("I don't manage!")

# 🚨 Most methods throw exceptions - BAD!
```

***

### ✅ GOOD: Small, Focused Interfaces

```python theme={null}
from abc import ABC, abstractmethod

# Split into small, focused interfaces
class Workable(ABC):
    @abstractmethod
    def work(self):
        pass

class Eatable(ABC):
    @abstractmethod
    def eat(self):
        pass

class Meetable(ABC):
    @abstractmethod
    def attend_meeting(self):
        pass

class Codeable(ABC):
    @abstractmethod
    def code(self):
        pass

class Designable(ABC):
    @abstractmethod
    def design(self):
        pass

class Manageable(ABC):
    @abstractmethod
    def manage_team(self):
        pass

# DESIGN REASONING: Each interface represents one capability.
# Classes compose only the interfaces they genuinely support.
# No NotImplementedError, no dead methods, no lies in the API.

class Developer(Workable, Eatable, Meetable, Codeable):
    def work(self):
        print("💻 Working on features...")
    
    def eat(self):
        print("🍕 Eating pizza...")
    
    def attend_meeting(self):
        print("🗣️ In standup...")
    
    def code(self):
        print("🐍 Writing Python...")

class Designer(Workable, Eatable, Meetable, Designable):
    def work(self):
        print("🎨 Creating mockups...")
    
    def eat(self):
        print("🥗 Eating salad...")
    
    def attend_meeting(self):
        print("🗣️ In design review...")
    
    def design(self):
        print("🖌️ Designing UI...")

class Manager(Workable, Eatable, Meetable, Manageable):
    def work(self):
        print("📊 Planning sprints...")
    
    def eat(self):
        print("☕ Coffee meeting...")
    
    def attend_meeting(self):
        print("🗣️ Leading meeting...")
    
    def manage_team(self):
        print("👥 Managing team...")

class Robot(Workable, Codeable):
    """Robot only works and codes - no eating or meetings!"""
    
    def work(self):
        print("🤖 Processing tasks...")
    
    def code(self):
        print("🤖 Auto-generating code...")

# 🎉 No NotImplementedError needed!
# Each class only has methods it actually uses
```

***

## 🖨️ Classic Example: Printer Interfaces

### ❌ BAD: Multifunction Monster

```python theme={null}
class MultifunctionDevice(ABC):
    """All devices must do EVERYTHING!"""
    
    @abstractmethod
    def print_doc(self, document):
        pass
    
    @abstractmethod
    def scan_doc(self, document):
        pass
    
    @abstractmethod
    def fax_doc(self, document):
        pass
    
    @abstractmethod
    def copy_doc(self, document):
        pass
    
    @abstractmethod
    def email_doc(self, document, email):
        pass

class OldPrinter(MultifunctionDevice):
    """Old printer can only print! 😢"""
    
    def print_doc(self, document):
        print(f"🖨️ Printing: {document}")
    
    def scan_doc(self, document):
        raise Exception("Can't scan!")  # 😰
    
    def fax_doc(self, document):
        raise Exception("Can't fax!")  # 😰
    
    def copy_doc(self, document):
        raise Exception("Can't copy!")  # 😰
    
    def email_doc(self, document, email):
        raise Exception("Can't email!")  # 😰
```

### ✅ GOOD: Segregated Interfaces

```python theme={null}
from abc import ABC, abstractmethod

class Printer(ABC):
    @abstractmethod
    def print_doc(self, document):
        pass

class Scanner(ABC):
    @abstractmethod
    def scan_doc(self) -> str:
        pass

class Fax(ABC):
    @abstractmethod
    def fax_doc(self, document, number):
        pass

class Copier(ABC):
    @abstractmethod
    def copy_doc(self, document) -> str:
        pass

class Emailer(ABC):
    @abstractmethod
    def email_doc(self, document, email):
        pass

# Simple printer - only prints!
class BasicPrinter(Printer):
    def print_doc(self, document):
        print(f"🖨️ Printing: {document}")

# Scanner - only scans!
class BasicScanner(Scanner):
    def scan_doc(self):
        return "📄 Scanned document content"

# All-in-one for those who need everything
class AllInOnePrinter(Printer, Scanner, Copier, Fax, Emailer):
    def print_doc(self, document):
        print(f"🖨️ Printing: {document}")
    
    def scan_doc(self):
        print("📸 Scanning...")
        return "Scanned content"
    
    def copy_doc(self, document):
        print("📋 Copying...")
        return document
    
    def fax_doc(self, document, number):
        print(f"📠 Faxing to {number}...")
    
    def email_doc(self, document, email):
        print(f"📧 Emailing to {email}...")

# Functions request only what they need
def print_report(printer: Printer, report):
    printer.print_doc(report)

def scan_and_save(scanner: Scanner):
    content = scanner.scan_doc()
    return content

# Works with any device that can print!
print_report(BasicPrinter(), "Sales Report")
print_report(AllInOnePrinter(), "Sales Report")

# Works with any device that can scan!
scan_and_save(AllInOnePrinter())
# scan_and_save(BasicPrinter())  # ❌ Type error - BasicPrinter can't scan
```

***

## 🎮 Real Example: Game Characters

```python theme={null}
from abc import ABC, abstractmethod

# Segregated ability interfaces
class Movable(ABC):
    @abstractmethod
    def move(self, direction):
        pass

class Attackable(ABC):
    @abstractmethod
    def attack(self, target):
        pass

class Healable(ABC):
    @abstractmethod
    def heal(self, target):
        pass

class Flyable(ABC):
    @abstractmethod
    def fly(self):
        pass

class Swimmable(ABC):
    @abstractmethod
    def swim(self):
        pass

class Castable(ABC):
    @abstractmethod
    def cast_spell(self, spell, target):
        pass

# Characters implement only what they can do!

class Warrior(Movable, Attackable):
    def move(self, direction):
        print(f"⚔️ Warrior walking {direction}")
    
    def attack(self, target):
        print(f"⚔️ Warrior slashes {target}!")

class Mage(Movable, Attackable, Castable, Healable):
    def move(self, direction):
        print(f"🧙 Mage walking {direction}")
    
    def attack(self, target):
        print(f"🧙 Mage hits {target} with staff!")
    
    def cast_spell(self, spell, target):
        print(f"✨ Mage casts {spell} on {target}!")
    
    def heal(self, target):
        print(f"💚 Mage heals {target}!")

class Dragon(Movable, Attackable, Flyable):
    def move(self, direction):
        print(f"🐉 Dragon stomping {direction}")
    
    def attack(self, target):
        print(f"🐉 Dragon breathes fire on {target}!")
    
    def fly(self):
        print("🐉 Dragon soars into the sky!")

class Fish(Movable, Swimmable):
    def move(self, direction):
        print(f"🐟 Fish swimming {direction}")
    
    def swim(self):
        print("🐟 Fish diving deep!")

class Turret(Attackable):  # Can't move!
    def attack(self, target):
        print(f"🔫 Turret fires at {target}!")

# Game engine uses specific interfaces
def process_flying_units(units: list[Flyable]):
    for unit in units:
        unit.fly()

def process_attackers(units: list[Attackable]):
    for unit in units:
        unit.attack("enemy")

# Usage
flying_units = [Dragon()]  # Only things that can fly
process_flying_units(flying_units)

attackers = [Warrior(), Mage(), Dragon(), Turret()]  # All attackers
process_attackers(attackers)
```

***

## 📱 Real Example: Mobile App Features

```python theme={null}
from abc import ABC, abstractmethod

# Feature interfaces
class Photographable(ABC):
    @abstractmethod
    def take_photo(self):
        pass

class Callable(ABC):
    @abstractmethod
    def make_call(self, number):
        pass

class Textable(ABC):
    @abstractmethod
    def send_text(self, number, message):
        pass

class GPSEnabled(ABC):
    @abstractmethod
    def get_location(self):
        pass

class BluetoothEnabled(ABC):
    @abstractmethod
    def connect_bluetooth(self, device):
        pass

class NFCEnabled(ABC):
    @abstractmethod
    def nfc_pay(self, amount):
        pass

# Different devices implement different features

class BasicPhone(Callable, Textable):
    """Old flip phone - calls and texts only!"""
    
    def make_call(self, number):
        print(f"📞 Calling {number}...")
    
    def send_text(self, number, message):
        print(f"💬 Texting {number}: {message}")

class SmartPhone(Callable, Textable, Photographable, GPSEnabled, BluetoothEnabled, NFCEnabled):
    """Modern smartphone - has everything!"""
    
    def make_call(self, number):
        print(f"📱 Video calling {number}...")
    
    def send_text(self, number, message):
        print(f"💬 iMessaging {number}: {message}")
    
    def take_photo(self):
        print("📸 Taking high-res photo!")
    
    def get_location(self):
        return {"lat": 40.7128, "lng": -74.0060}
    
    def connect_bluetooth(self, device):
        print(f"🔵 Connecting to {device}...")
    
    def nfc_pay(self, amount):
        print(f"💳 Paying ${amount} via NFC...")

class Camera(Photographable):
    """Dedicated camera - only takes photos!"""
    
    def take_photo(self):
        print("📷 Taking professional photo!")

class GPSDevice(GPSEnabled):
    """Dedicated GPS - only navigation!"""
    
    def get_location(self):
        return {"lat": 40.7128, "lng": -74.0060}

# Functions request only what they need
def take_photos(devices: list[Photographable]):
    for device in devices:
        device.take_photo()

def navigate(device: GPSEnabled):
    loc = device.get_location()
    print(f"📍 You are at {loc}")

# All of these work!
take_photos([SmartPhone(), Camera()])  # ✅
navigate(SmartPhone())  # ✅
navigate(GPSDevice())   # ✅
# navigate(BasicPhone())  # ❌ Type error - BasicPhone has no GPS
```

***

## 🔄 How to Apply ISP

<Steps>
  <Step title="Identify the fat interface">
    Look for interfaces with many methods that some implementations don't need
  </Step>

  <Step title="Group related methods">
    Which methods are always used together?
  </Step>

  <Step title="Create focused interfaces">
    One interface per group of related methods
  </Step>

  <Step title="Update implementations">
    Each class implements only the interfaces it needs
  </Step>
</Steps>

***

## 🧪 Practice Exercise

<Accordion title="Challenge: Fix the Vehicle Interface" icon="dumbbell">
  This interface is too fat. Split it!

  ```python theme={null}
  class Vehicle(ABC):
      @abstractmethod
      def start_engine(self):
          pass
      
      @abstractmethod
      def stop_engine(self):
          pass
      
      @abstractmethod
      def accelerate(self):
          pass
      
      @abstractmethod
      def brake(self):
          pass
      
      @abstractmethod
      def fly(self):
          pass
      
      @abstractmethod
      def sail(self):
          pass
      
      @abstractmethod
      def submerge(self):
          pass
      
      @abstractmethod
      def refuel(self):
          pass
      
      @abstractmethod
      def recharge(self):
          pass

  class Car(Vehicle):
      def fly(self):
          raise NotImplementedError("Cars can't fly!")
      
      def sail(self):
          raise NotImplementedError("Cars can't sail!")
      
      def submerge(self):
          raise NotImplementedError("Cars can't submerge!")
      
      def recharge(self):
          raise NotImplementedError("Gas cars don't recharge!")
      
      # ... lots of NotImplementedError

  class Bicycle(Vehicle):
      def start_engine(self):
          raise NotImplementedError("Bicycles have no engine!")
      # ... even more NotImplementedError! 😱
  ```

  <details>
    <summary>Click for Solution</summary>

    ```python theme={null}
    from abc import ABC, abstractmethod

    # Core movement interfaces
    class Driveable(ABC):
        @abstractmethod
        def accelerate(self):
            pass
        
        @abstractmethod
        def brake(self):
            pass

    class Flyable(ABC):
        @abstractmethod
        def take_off(self):
            pass
        
        @abstractmethod
        def land(self):
            pass

    class Sailable(ABC):
        @abstractmethod
        def sail(self):
            pass

    class Submersible(ABC):
        @abstractmethod
        def submerge(self):
            pass
        
        @abstractmethod
        def surface(self):
            pass

    # Power interfaces
    class EngineVehicle(ABC):
        @abstractmethod
        def start_engine(self):
            pass
        
        @abstractmethod
        def stop_engine(self):
            pass

    class FuelPowered(ABC):
        @abstractmethod
        def refuel(self):
            pass

    class ElectricPowered(ABC):
        @abstractmethod
        def recharge(self):
            pass

    class HumanPowered(ABC):
        @abstractmethod
        def pedal(self):
            pass

    # Now vehicles implement only what they need!

    class Car(EngineVehicle, FuelPowered, Driveable):
        def start_engine(self):
            print("🚗 Vroom!")
        
        def stop_engine(self):
            print("🚗 Engine off")
        
        def accelerate(self):
            print("🚗 Speeding up!")
        
        def brake(self):
            print("🚗 Slowing down!")
        
        def refuel(self):
            print("⛽ Filling up gas...")

    class ElectricCar(EngineVehicle, ElectricPowered, Driveable):
        def start_engine(self):
            print("⚡ Silently starting...")
        
        def stop_engine(self):
            print("⚡ Powering down...")
        
        def accelerate(self):
            print("⚡ Instant torque!")
        
        def brake(self):
            print("⚡ Regenerative braking!")
        
        def recharge(self):
            print("🔌 Charging battery...")

    class Bicycle(HumanPowered, Driveable):
        def pedal(self):
            print("🚲 Pedaling!")
        
        def accelerate(self):
            print("🚲 Pedaling faster!")
        
        def brake(self):
            print("🚲 Squeezing brakes!")

    class Airplane(EngineVehicle, FuelPowered, Flyable, Driveable):
        def start_engine(self):
            print("✈️ Engines starting!")
        
        def stop_engine(self):
            print("✈️ Engines stopping!")
        
        def take_off(self):
            print("✈️ Taking off!")
        
        def land(self):
            print("✈️ Landing!")
        
        def accelerate(self):
            print("✈️ Speeding up runway!")
        
        def brake(self):
            print("✈️ Deploying air brakes!")
        
        def refuel(self):
            print("⛽ Refueling jet fuel...")

    class Submarine(EngineVehicle, FuelPowered, Submersible):
        def start_engine(self):
            print("🚢 Submarine engines on!")
        
        def stop_engine(self):
            print("🚢 Engines silent!")
        
        def submerge(self):
            print("🚢 Diving!")
        
        def surface(self):
            print("🚢 Surfacing!")
        
        def refuel(self):
            print("⛽ Refueling nuclear reactor...")

    # 🎉 No NotImplementedError anywhere!
    ```
  </details>
</Accordion>

***

## 📝 Key Takeaways

<CardGroup cols={2}>
  <Card title="Small > Big" icon="minimize">
    Many small interfaces beat one big one
  </Card>

  <Card title="No Forced Methods" icon="ban">
    Classes shouldn't implement what they don't need
  </Card>

  <Card title="Combine as Needed" icon="object-group">
    Classes can implement multiple interfaces
  </Card>

  <Card title="Client-Focused" icon="user">
    Interfaces should fit what clients actually need
  </Card>
</CardGroup>

***

## Why ISP Matters in Production

In real codebases, fat interfaces cause a subtle but painful problem: forced coupling. If `UserService` depends on a `DataStore` interface with 30 methods (read, write, delete, batch, stream, replicate...), but `UserService` only ever calls `read()` and `write()`, it is still *coupled* to all 30 methods. When someone adds a 31st method to `DataStore`, `UserService`'s tests might break even though it uses none of the new functionality. ISP prevents this -- by depending on a narrow `ReadWriteStore` interface with just 2 methods, `UserService` is insulated from changes it does not care about.

**A senior engineer would say:** "ISP is really about coupling management. The wider the interface you depend on, the more reasons your code has to change. Narrow interfaces minimize your exposure to other teams' changes."

***

## Interview Insight

<Info>
  **ISP shows up in interviews as the "what if we add a new feature?" question.** When you design a system with a monolithic interface and the interviewer says "now support a device that can only do X but not Y," your design either handles it gracefully or crumbles. If you split interfaces upfront, adding a printer-only device that does not scan is trivial: it implements `Printable` and ignores `Scannable`. If you used a fat `MultiFunctionDevice` interface, you are stuck with `NotImplementedError`. The ISP-aware design also connects to LSP -- classes that implement an interface should actually support all its methods, and small interfaces make that achievable. When presenting your design, say: "I am keeping this interface narrow so implementors are not forced to support operations they cannot perform."
</Info>

***

## 🏃 Next: Dependency Inversion Principle

Let's learn the final SOLID principle - how to depend on abstractions!

<Card title="Continue to Dependency Inversion →" icon="arrow-right" href="/lld/solid/dip">
  Learn how to make your code flexible by depending on abstractions!
</Card>
