> ## 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.

# Polymorphism

> One interface, many forms - like a shape-shifter in code!

## 🦎 What is Polymorphism?

The word **polymorphism** comes from Greek:

* **Poly** = Many
* **Morph** = Forms

So polymorphism means **"many forms"**!

Imagine a **universal remote control** 📺. You press the "ON" button and:

* For a **TV**: It turns on the TV
* For a **AC**: It turns on the air conditioner
* For **Lights**: It turns on the lights

**Same button, different results** based on what you're controlling!

<Tip>
  **Simple Definition**: Polymorphism = Same method name, different behavior depending on the object
</Tip>

**A deeper analogy:** Think of a power outlet in your wall. The outlet provides one standard interface (two or three prongs). You can plug in a lamp, a phone charger, a blender, or a laptop -- each device does something completely different with the same electricity. The outlet does not need to know what is plugged in, and you do not need a different outlet for each device. That is polymorphism: one interface, unlimited implementations.

***

## 🔊 The Classic Shape Example

```python theme={null}
class Shape:
    def area(self):
        pass  # Each shape calculates differently
    
    def draw(self):
        pass  # Each shape draws differently

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius
    
    def area(self):
        return 3.14159 * self.radius ** 2
    
    def draw(self):
        print("⭕ Drawing a circle")

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height
    
    def area(self):
        return self.width * self.height
    
    def draw(self):
        print("⬜ Drawing a rectangle")

class Triangle(Shape):
    def __init__(self, base, height):
        self.base = base
        self.height = height
    
    def area(self):
        return 0.5 * self.base * self.height
    
    def draw(self):
        print("🔺 Drawing a triangle")

# THE MAGIC OF POLYMORPHISM ✨
# Same code works for ANY shape!

shapes = [
    Circle(5),
    Rectangle(4, 6),
    Triangle(3, 8)
]

print("Drawing all shapes and calculating areas:\n")
for shape in shapes:
    shape.draw()  # Same method call
    print(f"   Area: {shape.area():.2f}\n")  # Different results!
```

**Output:**

```
Drawing all shapes and calculating areas:

⭕ Drawing a circle
   Area: 78.54

⬜ Drawing a rectangle
   Area: 24.00

🔺 Drawing a triangle
   Area: 12.00
```

***

## 🎵 Real-World Example: Music Player

Imagine a music player that can play different types of audio:

```python theme={null}
class AudioFile:
    def __init__(self, filename):
        self.filename = filename
    
    def play(self):
        raise NotImplementedError("Subclass must implement this!")

class MP3File(AudioFile):
    def play(self):
        print(f"🎵 Playing MP3: {self.filename}")
        print("   Using MP3 decoder...")

class WAVFile(AudioFile):
    def play(self):
        print(f"🎵 Playing WAV: {self.filename}")
        print("   Raw audio, high quality!")

class FLACFile(AudioFile):
    def play(self):
        print(f"🎵 Playing FLAC: {self.filename}")
        print("   Lossless compression!")

class OGGFile(AudioFile):
    def play(self):
        print(f"🎵 Playing OGG: {self.filename}")
        print("   Open source format!")

# Music Player doesn't care about file type!
class MusicPlayer:
    def __init__(self):
        self.playlist = []
    
    def add_to_playlist(self, audio_file):
        self.playlist.append(audio_file)
    
    def play_all(self):
        print("=" * 40)
        print("🎧 Now Playing Your Playlist")
        print("=" * 40)
        for track in self.playlist:
            track.play()  # Polymorphism! Same method, different behavior
            print()

# Create playlist with different file types
player = MusicPlayer()
player.add_to_playlist(MP3File("song1.mp3"))
player.add_to_playlist(WAVFile("sound_effect.wav"))
player.add_to_playlist(FLACFile("classical.flac"))
player.add_to_playlist(OGGFile("podcast.ogg"))

player.play_all()
```

***

## 🎮 Fun Example: Game Attacks

Every character attacks differently, but the game just calls `attack()`!

```python theme={null}
class Character:
    def __init__(self, name, health):
        self.name = name
        self.health = health
    
    def attack(self, target):
        raise NotImplementedError("Each character attacks differently!")
    
    def take_damage(self, amount):
        self.health -= amount
        status = "💀 Defeated!" if self.health <= 0 else f"❤️ {self.health} HP left"
        print(f"   {self.name}: {status}")

class Warrior(Character):
    def attack(self, target):
        print(f"⚔️ {self.name} slashes with sword!")
        target.take_damage(25)

class Mage(Character):
    def attack(self, target):
        print(f"🔥 {self.name} casts Fireball!")
        target.take_damage(35)

class Archer(Character):
    def attack(self, target):
        print(f"🏹 {self.name} shoots an arrow!")
        target.take_damage(20)

class Healer(Character):
    def attack(self, target):
        print(f"✨ {self.name} throws holy light!")
        target.take_damage(10)
    
    def heal(self, target):
        print(f"💚 {self.name} heals {target.name}!")
        target.health += 30

class Ninja(Character):
    def attack(self, target):
        print(f"🌀 {self.name} appears behind the enemy!")
        print(f"   Triple strike combo!")
        target.take_damage(15)
        target.take_damage(15)
        target.take_damage(15)

# THE BATTLE!
def battle_round(attackers, defender):
    print("\n" + "=" * 50)
    print(f"🎯 Target: {defender.name}")
    print("=" * 50)
    
    for attacker in attackers:
        attacker.attack(defender)  # Same call, different attacks!
        if defender.health <= 0:
            print(f"\n🏆 {defender.name} has been defeated!")
            return

# Create team
team = [
    Warrior("Conan"),
    Mage("Gandalf"),
    Archer("Legolas"),
    Ninja("Naruto")
]

# Create enemy
dragon = Character("Dragon", 200)
dragon.health = 200

# Battle!
battle_round(team, dragon)
```

***

## 📚 Types of Polymorphism

<CardGroup cols={2}>
  <Card title="Method Overriding" icon="rotate">
    **Same method name in parent and child**

    Child provides its own version

    ```python theme={null}
    class Animal:
        def speak(self): pass

    class Dog(Animal):
        def speak(self):
            print("Woof!")
    ```
  </Card>

  <Card title="Duck Typing" icon="duck">
    **If it walks like a duck...**

    Python doesn't check type, just method

    ```python theme={null}
    def make_it_speak(thing):
        thing.speak()  # Works if has speak()
    ```
  </Card>
</CardGroup>

### 🦆 Duck Typing Explained

> "If it walks like a duck and quacks like a duck, it must be a duck!"

Python doesn't care about the TYPE, only about whether the object has the method:

```python theme={null}
class Duck:
    def quack(self):
        print("Quack! 🦆")
    
    def walk(self):
        print("Waddle waddle 🚶")

class Person:
    def quack(self):
        print("I'm pretending to be a duck! 🗣️")
    
    def walk(self):
        print("Walking normally 🚶")

class Robot:
    def quack(self):
        print("QUACK.exe executed 🤖")
    
    def walk(self):
        print("*mechanical walking sounds* 🦿")

# This function works with ANYTHING that can quack and walk
def do_duck_things(creature):
    creature.quack()
    creature.walk()

# All of these work!
print("=== Real Duck ===")
do_duck_things(Duck())

print("\n=== Person ===")
do_duck_things(Person())

print("\n=== Robot ===")
do_duck_things(Robot())
```

***

## 🎨 Practical Example: Drawing Application

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

class Drawable(ABC):
    """Anything that can be drawn on screen"""
    
    @abstractmethod
    def draw(self):
        pass
    
    @abstractmethod
    def get_area(self):
        pass

class Circle(Drawable):
    def __init__(self, x, y, radius, color):
        self.x = x
        self.y = y
        self.radius = radius
        self.color = color
    
    def draw(self):
        print(f"⭕ Circle at ({self.x}, {self.y})")
        print(f"   Radius: {self.radius}, Color: {self.color}")
    
    def get_area(self):
        return 3.14159 * self.radius ** 2

class Rectangle(Drawable):
    def __init__(self, x, y, width, height, color):
        self.x = x
        self.y = y
        self.width = width
        self.height = height
        self.color = color
    
    def draw(self):
        print(f"⬜ Rectangle at ({self.x}, {self.y})")
        print(f"   Size: {self.width}x{self.height}, Color: {self.color}")
    
    def get_area(self):
        return self.width * self.height

class Text(Drawable):
    def __init__(self, x, y, content, font_size):
        self.x = x
        self.y = y
        self.content = content
        self.font_size = font_size
    
    def draw(self):
        print(f"📝 Text at ({self.x}, {self.y})")
        print(f"   \"{self.content}\" (size: {self.font_size})")
    
    def get_area(self):
        # Text area is approximate
        return len(self.content) * self.font_size * 0.5

class Canvas:
    def __init__(self, name):
        self.name = name
        self.elements = []
    
    def add(self, drawable):
        self.elements.append(drawable)
    
    def render(self):
        print(f"\n🖼️ Rendering Canvas: {self.name}")
        print("=" * 40)
        for element in self.elements:
            element.draw()  # Polymorphism!
            print()
    
    def total_area(self):
        return sum(e.get_area() for e in self.elements)

# Create a drawing
canvas = Canvas("My Artwork")
canvas.add(Circle(100, 100, 50, "red"))
canvas.add(Rectangle(200, 50, 80, 60, "blue"))
canvas.add(Text(50, 200, "Hello World!", 24))
canvas.add(Circle(300, 300, 30, "green"))

canvas.render()
print(f"📐 Total covered area: {canvas.total_area():.2f}")
```

***

## 💳 Example: Payment System

Different payment methods, same interface:

```python theme={null}
class PaymentMethod:
    def pay(self, amount):
        raise NotImplementedError
    
    def get_name(self):
        raise NotImplementedError

class CreditCard(PaymentMethod):
    def __init__(self, card_number, cvv):
        self.card_number = card_number[-4:]  # Only store last 4
    
    def pay(self, amount):
        print(f"💳 Credit Card ending in {self.card_number}")
        print(f"   Processing ${amount:.2f}...")
        print(f"   ✅ Payment successful!")
        return True
    
    def get_name(self):
        return f"Card ***{self.card_number}"

class PayPal(PaymentMethod):
    def __init__(self, email):
        self.email = email
    
    def pay(self, amount):
        print(f"🅿️ PayPal ({self.email})")
        print(f"   Redirecting to PayPal...")
        print(f"   ✅ ${amount:.2f} paid via PayPal!")
        return True
    
    def get_name(self):
        return f"PayPal: {self.email}"

class Crypto(PaymentMethod):
    def __init__(self, wallet_address):
        self.wallet = wallet_address[:8] + "..."
    
    def pay(self, amount):
        btc_amount = amount / 50000  # Fake conversion
        print(f"₿ Crypto Wallet ({self.wallet})")
        print(f"   Converting ${amount:.2f} to {btc_amount:.6f} BTC...")
        print(f"   ⏳ Waiting for blockchain confirmation...")
        print(f"   ✅ Transaction confirmed!")
        return True
    
    def get_name(self):
        return f"Crypto: {self.wallet}"

class ApplePay(PaymentMethod):
    def __init__(self, device_name):
        self.device = device_name
    
    def pay(self, amount):
        print(f"🍎 Apple Pay ({self.device})")
        print(f"   Authenticating with Face ID...")
        print(f"   ✅ ${amount:.2f} paid!")
        return True
    
    def get_name(self):
        return f"Apple Pay: {self.device}"

# Checkout system - doesn't care WHICH payment method!
class Checkout:
    def __init__(self):
        self.cart_total = 0
    
    def process_payment(self, payment_method, amount):
        print("\n" + "=" * 50)
        print(f"🛒 Checkout - Total: ${amount:.2f}")
        print(f"📱 Using: {payment_method.get_name()}")
        print("=" * 50)
        
        # POLYMORPHISM! Same call, different behavior
        success = payment_method.pay(amount)
        
        if success:
            print("\n🎉 Thank you for your purchase!")
        return success

# Test different payment methods
checkout = Checkout()

# Same checkout process, different payment methods
checkout.process_payment(CreditCard("1234567890123456", "123"), 99.99)
checkout.process_payment(PayPal("user@email.com"), 49.99)
checkout.process_payment(Crypto("bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh"), 199.99)
checkout.process_payment(ApplePay("iPhone 15"), 29.99)
```

***

## Why Polymorphism Is the Heart of Good Design

Polymorphism is not just a convenience -- it is the mechanism that makes the Open/Closed Principle (which you will learn in the SOLID section) possible. When your code depends on an abstract interface like `PaymentMethod.pay()` rather than a concrete class like `CreditCard.charge_visa()`, you can add new payment methods (Apple Pay, crypto, buy-now-pay-later) without modifying the checkout code at all.

**A senior engineer would say:** "If encapsulation protects your data and inheritance organizes your types, polymorphism is what makes your system genuinely extensible. Every well-designed plugin system, every driver architecture, every strategy pattern -- they all depend on polymorphism."

**Production example:** Consider how Python's `sorted()` function works. It does not know whether it is sorting integers, strings, or custom objects. It just calls each object's comparison methods. That is polymorphism in the standard library -- the sorting algorithm is written once but works with infinite types.

***

## Benefits of Polymorphism

| Benefit             | Description                                  |
| ------------------- | -------------------------------------------- |
| **Flexibility**     | Add new types without changing existing code |
| **Simplicity**      | One interface for many implementations       |
| **Maintainability** | Changes in one class don't affect others     |
| **Testability**     | Easy to swap implementations for testing     |

***

## 🧪 Practice Exercise

<Accordion title="Challenge: Build a Notification System" icon="bell">
  Create a notification system that can send messages through different channels:

  1. **EmailNotification**: Sends via email
  2. **SMSNotification**: Sends via text message
  3. **PushNotification**: Sends to mobile app
  4. **SlackNotification**: Sends to Slack channel

  All should have a `send(message)` method!

  ```python theme={null}
  class Notification:
      def send(self, message):
          raise NotImplementedError

  class EmailNotification(Notification):
      def __init__(self, email):
          # TODO
          pass
      
      def send(self, message):
          # TODO: Print sending email
          pass

  # TODO: Add SMS, Push, Slack notifications

  # Test with NotificationService
  class NotificationService:
      def notify_all(self, channels, message):
          for channel in channels:
              channel.send(message)

  # Test
  service = NotificationService()
  channels = [
      EmailNotification("user@example.com"),
      SMSNotification("+1234567890"),
      PushNotification("user_device_123"),
      SlackNotification("#general")
  ]
  service.notify_all(channels, "Your order has shipped!")
  ```

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

    ```python theme={null}
    class Notification:
        def send(self, message):
            raise NotImplementedError

    class EmailNotification(Notification):
        def __init__(self, email):
            self.email = email
        
        def send(self, message):
            print(f"📧 Email to {self.email}")
            print(f"   Subject: Notification")
            print(f"   Body: {message}")

    class SMSNotification(Notification):
        def __init__(self, phone):
            self.phone = phone
        
        def send(self, message):
            print(f"📱 SMS to {self.phone}")
            print(f"   Message: {message[:160]}")  # SMS limit

    class PushNotification(Notification):
        def __init__(self, device_id):
            self.device_id = device_id
        
        def send(self, message):
            print(f"🔔 Push to device {self.device_id}")
            print(f"   Alert: {message}")

    class SlackNotification(Notification):
        def __init__(self, channel):
            self.channel = channel
        
        def send(self, message):
            print(f"💬 Slack to {self.channel}")
            print(f"   Message: {message}")

    class NotificationService:
        def notify_all(self, channels, message):
            print("=" * 50)
            print("📣 Sending Notifications...")
            print("=" * 50)
            for channel in channels:
                channel.send(message)
                print()

    # Test
    service = NotificationService()
    channels = [
        EmailNotification("user@example.com"),
        SMSNotification("+1234567890"),
        PushNotification("device_abc123"),
        SlackNotification("#general")
    ]
    service.notify_all(channels, "Your order has shipped!")
    ```
  </details>
</Accordion>

***

## Interview Insight

<Info>
  **Key interview pattern:** When designing any system in an LLD interview, polymorphism is your secret weapon for handling variability. The interviewer says "now what if we need to support a new notification channel?" If your design uses polymorphism (a `NotificationChannel` interface with `send()`), the answer is "just add a new class that implements the interface." If your design uses if/elif chains, the answer is "modify existing code and hope nothing breaks." The first answer demonstrates extensible design; the second demonstrates brittle design. Always structure your LLD answers so that new requirements are handled by *adding* new classes, not by *modifying* existing ones. That is polymorphism in action.
</Info>

***

## Interview Deep-Dive

<AccordionGroup>
  <Accordion title="An interviewer says: 'Explain how polymorphism supports the Open/Closed Principle with a concrete example from a system you would design.'">
    **Strong Answer:**

    * Polymorphism is the mechanism that makes OCP work. When you define a NotificationChannel interface with send(message), the NotificationService iterates over channels and calls send() on each one. Adding a new channel (WhatsApp, Telegram, Discord) means creating a new class that implements the interface. The NotificationService never changes -- it is closed for modification but open for extension.
    * Without polymorphism, adding a new channel means adding an elif branch to the service: "if channel == 'whatsapp': send\_whatsapp()" -- modifying existing code, risking regressions, and increasing cyclomatic complexity.
    * In the payment system from this page, the Checkout class calls payment\_method.pay(amount) polymorphically. It works with CreditCard, PayPal, Crypto, and ApplePay without knowing or caring which one. When the business adds GooglePay next quarter, zero lines of Checkout code change.
    * This is why I always reach for an interface/ABC when I see behavior that varies across types. It is the single most impactful design decision in an LLD interview.

    **Follow-up: Is there a cost to polymorphism? When is a simple if/elif chain actually better?**

    Polymorphism adds indirection: more classes, more files, and the actual implementation is resolved at runtime. For two or three types that rarely change, an if/elif chain is simpler, more readable, and faster to write. I reach for polymorphism when I expect the list of types to grow, when different teams own different implementations, or when I need to test each implementation in isolation. The crossover point is usually around three to four types.
  </Accordion>

  <Accordion title="Python uses duck typing. How does this compare to Java's interface-based polymorphism, and what are the trade-offs for LLD design?">
    **Strong Answer:**

    * In Java, polymorphism requires an explicit contract: the class must declare that it implements the interface. If MusicPlayer expects AudioFile, only classes that extend/implement AudioFile can be passed. The compiler enforces this at compile time.
    * In Python, duck typing means any object with a play() method can be passed to a function that calls play(), regardless of whether it formally implements an AudioFile interface. This is more flexible but less safe -- you find type errors at runtime, not compile time.
    * For LLD design, I use ABCs (abstract base classes) to get the best of both worlds. By defining an AudioFile ABC with @abstractmethod play(), I get: (1) documentation of the contract, (2) an error at instantiation time if a subclass forgets to implement play(), and (3) compatibility with isinstance() checks and type hints.
    * The trade-off: ABCs add ceremony (import ABC, add decorators, define abstract methods) but provide safety and documentation. Pure duck typing is faster to write but harder for a new team member to understand -- they must read the code to discover what methods are expected.

    **Follow-up: What is Python's Protocol (from typing module) and when would you use it instead of ABC?**

    Protocol provides structural subtyping -- like duck typing but with static type checking. A class satisfies a Protocol if it has the required methods, without explicitly inheriting from it. Use Protocol when you want type checking for third-party classes you do not control (you cannot make them inherit from your ABC). Use ABC when you control the hierarchy and want runtime enforcement via isinstance().
  </Accordion>

  <Accordion title="In the Canvas example, total_area() calls e.get_area() for every element including Text. But Text's area calculation is approximate. How would you handle this in a production system?">
    **Strong Answer:**

    * This is a subtle LSP concern. The Shape contract implies area() returns the precise geometric area. Text's implementation returns an approximation. If consumer code depends on precision (e.g., for physics simulation or cost calculation), Text violates the contract.
    * Solution 1: Separate the interfaces. Create a MeasurableArea interface for shapes with precise area calculations, and a separate Renderable interface for things that can be drawn. Text implements Renderable but not MeasurableArea. The total\_area() function accepts only MeasurableArea objects, excluding Text.
    * Solution 2: Make the approximation explicit. Add a is\_area\_exact() method to the interface that returns True for geometric shapes and False for Text. The caller can decide whether to include approximate areas.
    * Solution 3: Use the Visitor pattern. A CanvasAreaCalculator visitor visits each element and decides how to handle it based on type -- skipping Text or flagging it as approximate. This centralizes the decision logic.
    * The key insight: polymorphism works best when all implementations truly honor the same contract. When they do not, the right fix is to split the interface (ISP), not to paper over the difference.

    **Follow-up: This sounds like Interface Segregation. How do all four OOP pillars connect in a real design?**

    They reinforce each other. Encapsulation protects each class's internal state. Inheritance or composition defines the hierarchy. Polymorphism enables uniform treatment of different types. Abstraction defines the contracts that polymorphism relies on. When one pillar is weak (e.g., polymorphism with a leaky contract), the fix often comes from another pillar (abstraction via ISP). A senior engineer thinks about all four simultaneously, not in isolation.
  </Accordion>
</AccordionGroup>

***

## 🏃 Next Up: Abstraction

Now let's learn how to hide complex details and show only what's necessary!

<Card title="Continue to Abstraction →" icon="eye-slash" href="/lld/oop/abstraction">
  Learn how to hide complexity and create simple interfaces - like a car dashboard!
</Card>
