Polymorphism in Python: Duck Typing, Dunder Methods & singledispatch (Interview Guide)
Quick answer
Polymorphism means the same operation behaves differently depending on the object it acts on. Python leans on duck typing: any object implementing the expected method works, no shared base class required. It also shows up as method overriding in subclasses, as operator overloading via dunder methods (__len__, __add__, __eq__) that make built-ins work on your types, and as function-level dispatch via functools.singledispatch. Python has no true method overloading — the last definition wins.
Short answer: Polymorphism means the same operation behaves differently depending on the object it acts on. Python's core mechanism is duck typing — any object with the expected method works, no shared base class required. It also appears via method overriding, dunder methods (__len__, __add__), and functools.singledispatch.
Most tutorials reduce polymorphism to "override a method in a subclass." That's one form, but in Python it undersells the concept — Python's polymorphism is mostly about duck typing, and interviewers probe whether you understand that.
Duck typing: the Pythonic core
"If it walks like a duck and quacks like a duck, treat it as a duck." Python doesn't check an object's type — it checks whether it has the method:
class Dog:
def speak(self): return "Woof!"
class Cat:
def speak(self): return "Meow!"
class Robot: # NOT related to Dog/Cat by inheritance
def speak(self): return "Beep!"
def make_it_speak(thing): # works for anything with .speak()
return thing.speak()
for t in (Dog(), Cat(), Robot()):
print(make_it_speak(t)) # Woof! / Meow! / Beep!There is no shared base class here — make_it_speak is polymorphic purely because each object has a speak() method. This is the key idea a strong answer leads with.
Method overriding (the inheritance flavour)
Polymorphism also appears when a subclass overrides a parent method — this relies on inheritance:
class Shape:
def area(self): raise NotImplementedError
class Circle(Shape):
def __init__(self, r): self.r = r
def area(self): return 3.14159 * self.r ** 2
class Rectangle(Shape):
def __init__(self, w, h): self.w, self.h = w, h
def area(self): return self.w * self.h
shapes = [Circle(5), Rectangle(2, 4)]
print([s.area() for s in shapes]) # [78.53975, 8] — same call, different behaviourOperator overloading via dunder methods
Dunder methods make your objects polymorphic with Python's built-in operators and functions:
class Vector:
def __init__(self, x, y): self.x, self.y = x, y
def __add__(self, other): # enables v1 + v2
return Vector(self.x + other.x, self.y + other.y)
def __eq__(self, other): # enables v1 == v2
return (self.x, self.y) == (other.x, other.y)
def __repr__(self): # enables print(v)
return f"Vector({self.x}, {self.y})"
print(Vector(1, 2) + Vector(3, 4)) # Vector(4, 6)len(), +, ==, in, iteration — all dispatch to dunder methods, so implementing them makes your type behave like a built-in.
Python has no method overloading (a classic trap)
Unlike Java/C++, defining two methods with the same name does not overload — the second simply replaces the first:
class C:
def greet(self): return "hi"
def greet(self, name): return f"hi {name}" # this one wins; the first is gone
C().greet("Sam") # works; C().greet() now raises TypeErrorAchieve the effect with default/variable arguments, or dispatch on type explicitly:
from functools import singledispatch
@singledispatch
def describe(x): return f"value: {x}"
@describe.register
def _(x: list): return f"list of {len(x)}"
@describe.register
def _(x: dict): return f"dict with keys {list(x)}"
describe(5) # value: 5
describe([1, 2]) # list of 2Formalising duck typing with Protocol
For static type checking without forcing inheritance, typing.Protocol (PEP 544) describes a shape:
from typing import Protocol
class Speaker(Protocol):
def speak(self) -> str: ...
def announce(s: Speaker) -> str: # any object with speak() satisfies this
return s.speak()Robot from earlier satisfies Speaker structurally, with no inheritance — duck typing that a type checker can verify.
Common interview traps
- Reducing polymorphism to inheritance — lead with duck typing; overriding is just one flavour.
- Expecting method overloading to work — the last
defwins. - Forgetting dunder polymorphism —
__len__/__eq__/__iter__are polymorphism with the standard library.
Interviewer follow-ups
- "How would you support the same function for
intandlistinputs?" —functools.singledispatch. - "How do abstract base classes fit in?" — see abstraction and ABCs in the OOP guide; an ABC enforces that subclasses implement the polymorphic method.
Related guides
Sources
Key takeaways
- •Duck typing is Python's core polymorphism: 'if it has the method, it works' — no shared base class needed.
- •Dunder methods (__len__, __add__, __eq__, __iter__) make your objects polymorphic with Python's built-ins.
- •Python has NO method overloading — a second def with the same name replaces the first; use defaults or functools.singledispatch.
- •typing.Protocol formalises duck typing for static type checkers without forcing inheritance.
Frequently asked questions
What is duck typing in Python?
Duck typing means an object's suitability is decided by the methods it has, not the class it inherits from — 'if it walks like a duck and quacks like a duck, treat it as a duck.' Any object with the expected method can be used, so polymorphism does not require a shared base class or interface.
Does Python support method overloading?
No. If you define two methods with the same name, the second replaces the first. You achieve similar results with default or variable arguments (*args, **kwargs), or with functools.singledispatch to dispatch on the type of the first argument.
How is polymorphism different from inheritance?
Inheritance is a mechanism (deriving one class from another to reuse behaviour). Polymorphism is a behaviour (the same call doing different things by object type). Overriding a method in a subclass uses inheritance to achieve polymorphism, but duck typing gives you polymorphism with no inheritance at all.
Software Engineering Leader & Technical Author · Updated August 26, 2026