Skip to content

Polymorphism

One interface, multiple implementations.

Polymorphism is the ability of different classes to be treated as instances of the same class through a common interface. It allows methods to do different things based on the object they’re acting upon, even though they share the same name.

The word “polymorphism” comes from Greek meaning “many forms”. In programming, it means that objects of different types can be accessed through the same interface.

  1. Duck Typing - “If it walks like a duck and quacks like a duck, it’s a duck”
  2. Method Overriding - Subclasses override parent methods
  3. Operator Overloading - Same operator works differently for different types

Python uses duck typing - if an object has the required methods, it can be used regardless of its type:

duck_typing.py
class Dog:
def speak(self):
return "Woof!"
class Cat:
def speak(self):
return "Meow!"
class Robot:
def speak(self):
return "Beep boop!"
def make_sound(animal):
"""Function works with any object that has a speak() method"""
print(animal.speak())
# All these work - they all have a speak() method
dog = Dog()
cat = Cat()
robot = Robot()
make_sound(dog) # "Woof!"
make_sound(cat) # "Meow!"
make_sound(robot) # "Beep boop!"

When classes inherit from a common base class, they can be used interchangeably:

inheritance_polymorphism.py
class Vehicle:
def __init__(self, brand: str, model: str, year: int):
self.brand = brand
self.model = model
self.year = year
def start(self):
"""Base implementation"""
return f"{self.brand} {self.model} started."
def get_info(self):
return f"{self.brand} {self.model}, Year: {self.year}"
class Car(Vehicle):
def start(self):
"""Polymorphic behavior - Car's version"""
return f"{self.brand} {self.model} car started with a roar!"
class Motorcycle(Vehicle):
def start(self):
"""Polymorphic behavior - Motorcycle's version"""
return f"{self.brand} {self.model} motorcycle started with a vroom!"
class ElectricVehicle(Vehicle):
def start(self):
"""Polymorphic behavior - Electric's version"""
return f"{self.brand} {self.model} silently started (electric motor)"
def start_vehicle(vehicle: Vehicle):
"""Function accepts any Vehicle - polymorphism in action"""
print(vehicle.start())
print(vehicle.get_info())
# All vehicles can be used interchangeably
car = Car("Toyota", "Camry", 2020)
motorcycle = Motorcycle("Yamaha", "YZF-R3", 2021)
electric = ElectricVehicle("Tesla", "Model 3", 2023)
start_vehicle(car) # Works - Car is a Vehicle
start_vehicle(motorcycle) # Works - Motorcycle is a Vehicle
start_vehicle(electric) # Works - ElectricVehicle is a Vehicle
classDiagram
    class Vehicle {
        +brand: str
        +model: str
        +year: int
        +start()* str
        +get_info() str
    }
    
    class Car {
        +start() str
    }
    
    class Motorcycle {
        +start() str
    }
    
    class ElectricVehicle {
        +start() str
    }
    
    Vehicle <|-- Car
    Vehicle <|-- Motorcycle
    Vehicle <|-- ElectricVehicle
    
    note for Vehicle "Polymorphism:\nSame interface,\ndifferent implementations"
payment_polymorphism.py
class PaymentMethod:
"""Base class for payment methods"""
def process_payment(self, amount: float) -> bool:
raise NotImplementedError("Subclass must implement process_payment")
class CreditCard(PaymentMethod):
def __init__(self, card_number: str, cvv: str):
self.card_number = card_number
self.cvv = cvv
def process_payment(self, amount: float) -> bool:
"""Process credit card payment"""
print(f"Processing ${amount:.2f} via credit card ending in {self.card_number[-4:]}")
# Credit card processing logic
return True
class PayPal(PaymentMethod):
def __init__(self, email: str):
self.email = email
def process_payment(self, amount: float) -> bool:
"""Process PayPal payment"""
print(f"Processing ${amount:.2f} via PayPal ({self.email})")
# PayPal processing logic
return True
class BankTransfer(PaymentMethod):
def __init__(self, account_number: str):
self.account_number = account_number
def process_payment(self, amount: float) -> bool:
"""Process bank transfer"""
print(f"Processing ${amount:.2f} via bank transfer (Account: {self.account_number})")
# Bank transfer logic
return True
class ShoppingCart:
"""Shopping cart that accepts any payment method"""
def __init__(self):
self.items = []
self.total = 0.0
def add_item(self, item: str, price: float):
self.items.append((item, price))
self.total += price
def checkout(self, payment_method: PaymentMethod) -> bool:
"""Polymorphic method - works with any PaymentMethod"""
print(f"Checking out {len(self.items)} items, Total: ${self.total:.2f}")
return payment_method.process_payment(self.total)
# Usage - polymorphism allows using different payment methods interchangeably
cart = ShoppingCart()
cart.add_item("Laptop", 999.99)
cart.add_item("Mouse", 29.99)
# All payment methods work the same way
credit_card = CreditCard("1234567890123456", "123")
paypal = PayPal("user@example.com")
bank_transfer = BankTransfer("ACC-12345")
cart.checkout(credit_card) # Works
cart.checkout(paypal) # Works
cart.checkout(bank_transfer) # Works

Using abstract classes ensures all implementations provide required methods:

abstract_polymorphism.py
from abc import ABC, abstractmethod
class Shape(ABC):
"""Abstract base class"""
@abstractmethod
def area(self) -> float:
pass
@abstractmethod
def perimeter(self) -> float:
pass
class Rectangle(Shape):
def __init__(self, width: float, height: float):
self.width = width
self.height = height
def area(self) -> float:
return self.width * self.height
def perimeter(self) -> float:
return 2 * (self.width + self.height)
class Circle(Shape):
def __init__(self, radius: float):
self.radius = radius
def area(self) -> float:
import math
return math.pi * self.radius ** 2
def perimeter(self) -> float:
import math
return 2 * math.pi * self.radius
class Triangle(Shape):
def __init__(self, a: float, b: float, c: float):
self.a = a
self.b = b
self.c = c
def area(self) -> float:
# Heron's formula
s = self.perimeter() / 2
return (s * (s - self.a) * (s - self.b) * (s - self.c)) ** 0.5
def perimeter(self) -> float:
return self.a + self.b + self.c
def print_shape_info(shape: Shape):
"""Polymorphic function - works with any Shape"""
print(f"Area: {shape.area():.2f}")
print(f"Perimeter: {shape.perimeter():.2f}")
# All shapes can be used interchangeably
rectangle = Rectangle(5, 3)
circle = Circle(4)
triangle = Triangle(3, 4, 5)
print_shape_info(rectangle) # Works
print_shape_info(circle) # Works
print_shape_info(triangle) # Works

The same operator can work differently for different types:

operator_overloading.py
class Vector:
def __init__(self, x: float, y: float):
self.x = x
self.y = y
def __add__(self, other):
"""+ operator - polymorphic behavior"""
if isinstance(other, Vector):
return Vector(self.x + other.x, self.y + other.y)
elif isinstance(other, (int, float)):
return Vector(self.x + other, self.y + other)
return NotImplemented
def __mul__(self, scalar):
"""* operator - polymorphic behavior"""
return Vector(self.x * scalar, self.y * scalar)
def __str__(self):
return f"Vector({self.x}, {self.y})"
v1 = Vector(1, 2)
v2 = Vector(3, 4)
# Same + operator, different behavior
result1 = v1 + v2 # Vector addition
result2 = v1 + 5 # Scalar addition
result3 = v1 * 3 # Scalar multiplication
print(result1) # Vector(4, 6)
print(result2) # Vector(6, 7)
print(result3) # Vector(3, 6)
notification_polymorphism.py
class Notification:
"""Base notification class"""
def send(self, message: str) -> bool:
raise NotImplementedError("Subclass must implement send()")
class EmailNotification(Notification):
def __init__(self, recipient: str):
self.recipient = recipient
def send(self, message: str) -> bool:
"""Email-specific implementation"""
print(f"Sending email to {self.recipient}: {message}")
return True
class SMSNotification(Notification):
def __init__(self, phone_number: str):
self.phone_number = phone_number
def send(self, message: str) -> bool:
"""SMS-specific implementation"""
print(f"Sending SMS to {self.phone_number}: {message[:50]}...")
return True
class PushNotification(Notification):
def __init__(self, device_id: str):
self.device_id = device_id
def send(self, message: str) -> bool:
"""Push notification-specific implementation"""
print(f"Sending push to device {self.device_id}: {message}")
return True
class NotificationService:
"""Service that can use any notification type"""
def __init__(self):
self.notifications = []
def add_notification(self, notification: Notification):
"""Add any notification type"""
self.notifications.append(notification)
def broadcast(self, message: str):
"""Send message through all notification channels"""
for notification in self.notifications:
notification.send(message) # Polymorphism - each type handles differently
# Usage
service = NotificationService()
service.add_notification(EmailNotification("user@example.com"))
service.add_notification(SMSNotification("+1234567890"))
service.add_notification(PushNotification("device-123"))
# All notifications work the same way
service.broadcast("Your order has been shipped!")
  1. Code Reusability - Write code once, use with multiple types
  2. Flexibility - Easy to add new types without changing existing code
  3. Maintainability - Changes to one type don’t affect others
  4. Simplicity - One interface for multiple implementations
  5. Extensibility - Easy to extend functionality

Polymorphism is about flexibility - writing code that works with multiple types without knowing the specific type at compile time. It’s one of the most powerful features of object-oriented programming.