bigonotes
CS ConceptsOOP

Polymorphism

How one interface can behave differently per object in Python: duck typing, method overriding and operator overloading.

2 min readLast updated OOPListen · Watch on NotebookLM

Polymorphism means "many forms": the same interface behaves differently depending on the object behind it. The + operator adds numbers but joins strings. Polymorphism is what lets you add new types to a system without rewriting the code that uses them.

Duck typing#

Python doesn't check an object's type before calling a method. It only checks whether the method exists. "If it walks like a duck and quacks like a duck, it's a duck."

class Dog:
    def speak(self):
        print("Woof!")

class Cat:
    def speak(self):
        print("Meow!")

class Duck:
    def speak(self):
        print("Quack!")

# No common parent at all
for animal in [Dog(), Cat(), Duck()]:
    animal.speak()
# Woof!
# Meow!
# Quack!

Python looks up speak on each object (instance, then its class and parents). If it finds it, it calls it; if not, you get AttributeError.

Method overriding#

Subclasses redefine a parent method with the same name:

import math

class Shape:
    def area(self):
        raise NotImplementedError

class Circle(Shape):
    def __init__(self, r):
        self.r = r

    def area(self):
        return math.pi * self.r ** 2

class Square(Shape):
    def __init__(self, side):
        self.side = side

    def area(self):
        return self.side ** 2

for shape in [Circle(5), Square(4)]:
    print(round(shape.area(), 2))
# 78.54
# 16

Overriding replaces the parent's behavior entirely. Extending calls super().method() first and then adds to it.

Operator overloading#

Operators are syntactic sugar for dunder methods:

2 + 3        # (2).__add__(3)
"hi" + "!"   # "hi".__add__("!")
[1] + [2]    # [1].__add__([2])

You can give your own classes operators:

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __add__(self, other):
        # Return a Vector (not a tuple) so v1 + v2 + v3 keeps working
        return Vector(self.x + other.x, self.y + other.y)

v = Vector(1, 2) + Vector(3, 4) + Vector(1, 1)
print(v.x, v.y)  # 5 7
Operator Dunder method Example
+ __add__ v1 + v2
- __sub__ v1 - v2
* __mul__ v1 * 2
== __eq__ v1 == v2
< __lt__ v1 < v2
> __gt__ v1 > v2
str(obj) __str__ print(v1)
len(obj) __len__ len(v1)

See Dunder Methods & @property for more.

Duck typing vs overriding#

Python gives you polymorphism in two ways:

Duck typing Method overriding
Needs a common parent? No Yes
Same method name? Yes Yes
Different behavior? Yes Yes
How the method is found Normal lookup on the object Child class first, then parent

Why it matters in design#

Polymorphism replaces type checks with a single call:

# Without polymorphism: every new type means editing this block
for animal in animals:
    if type(animal) == Dog:
        animal.bark()
    elif type(animal) == Cat:
        animal.meow()

# With polymorphism: new types just implement speak()
for animal in animals:
    animal.speak()

You'll see this in nearly every LLD problem:

  • Parking lot: different vehicle types behind one Vehicle interface.
  • Ride booking: different cab types, one calculate_fare().
  • Payments: different payment methods, one pay().

Key takeaways#

  • Polymorphism = same interface, different behavior.
  • Duck typing: Python checks that the method exists, not the type.
  • Overriding: a child redefines a parent's method; super() lets you extend it instead.
  • Every operator maps to a dunder method you can implement.
  • Return your own type from operator methods so expressions chain.
  • Polymorphism lets you add types without changing existing callers (the Open/Closed Principle).

Found a mistake? Tell me on LinkedIn ↗

Further reading#