python › Polymorphism in Python
phoolchandra · Oct 23, 2026 · Published
You are a student in class, a customer in a shop, and a son or daughter at home — one person, many forms. That is polymorphism (Greek: poly = many, morph = forms). In OOP, it means the same function or method name can behave differently depending on the object. In this blog you will learn compile-time and run-time polymorphism, method overloading and overriding, operator overloading and duck typing.
Watch the video first, then use this blog as your notes. Every section has runnable examples, and at the end you will find a "Predict the Output" quiz, practice challenges, a self-check list, interview questions and a quick summary.
How to use this blog: Open the Python shell (type python in your terminal) or any online Python editor and type every example yourself. Change the values, break the code and see what happens — that is how concepts really stick.
Polymorphism is the ability of one interface (function, method or operator) to work with different types of objects, and behave differently for each.
Polymorphism
/ \
Compile-time (static) Run-time (dynamic)
- Method overloading - Method overriding
- Operator overloading - Duck typingprint(len("abc")) # 3 - characters in a string
print(len([1, 2])) # 2 - items in a list
print(len({"a": 1})) # 1 - keys in a dict
print(3 + 4) # 7 - addition
print("3" + "4") # 34 - concatenation
print([1] + [2]) # [1, 2] - list joiningSame len() function, same + operator — different behaviour for each type. That is built-in polymorphism.
In Java/C++, method overloading means several methods with the same name but different parameters, and the compiler picks one. Python does not support this directly — if you define the same method twice, the last one replaces the first.
class Maths:
def add(self, a, b):
return a + b
def add(self, a, b, c): # this REPLACES the previous add
return a + b + c
m = Maths()
print(m.add(1, 2, 3)) # 6
print(m.add(1, 2)) # TypeError: missing 1 required positional argument: 'c'Option 1 — default arguments
class Calculator:
def add(self, a, b=0, c=0):
return a + b + c
calc = Calculator()
print(calc.add(1)) # 1
print(calc.add(1, 2)) # 3
print(calc.add(1, 2, 3)) # 6Option 2 — variable arguments (*args)
class Calculator:
def add(self, *numbers):
return sum(numbers)
print(Calculator().add(1, 2, 3, 4, 5)) # 15Option 3 — functools.singledispatchmethod (overload by type)
from functools import singledispatchmethod
class Printer:
@singledispatchmethod
def show(self, value):
print("Unknown type:", value)
@show.register
def _(self, value: int):
print("Integer:", value)
@show.register
def _(self, value: str):
print("String:", value)
p = Printer()
p.show(10) # Integer: 10
p.show("hi") # String: hi
p.show(2.5) # Unknown type: 2.5You can define how operators like +, ==, < work for your own classes using special (dunder) methods.
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other): # defines v1 + v2
return Vector(self.x + other.x, self.y + other.y)
def __eq__(self, other): # defines v1 == v2
return self.x == other.x and self.y == other.y
def __str__(self):
return f"Vector({self.x}, {self.y})"
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2) # Vector(4, 6)
print(v1 == Vector(1, 2)) # True| Operator | Magic method |
|---|---|
| + | __add__ |
| - | __sub__ |
| * | __mul__ |
| / | __truediv__ |
| == | __eq__ |
| < | __lt__ |
| > | __gt__ |
| len() | __len__ |
| str() / print | __str__ |
When a child class provides its own version of a method already defined in the parent class, it is called method overriding. Which version runs is decided at run time, based on the actual object.
class Animal:
def sound(self):
return "Some generic sound"
class Dog(Animal):
def sound(self): # overrides Animal.sound
return "Woof!"
class Cat(Animal):
def sound(self):
return "Meow!"
class Cow(Animal):
pass # does not override
for animal in [Dog(), Cat(), Cow()]:
print(type(animal).__name__, "->", animal.sound())Dog -> Woof!
Cat -> Meow!
Cow -> Some generic soundclass Employee:
def salary(self):
return 30000
class Manager(Employee):
def salary(self):
return super().salary() + 20000 # parent's salary + bonus
print(Manager().salary()) # 50000"If it walks like a duck and quacks like a duck, it's a duck." Python doesn't care about an object's type — only whether it has the required method. The classes don't even need a common parent.
class Duck:
def speak(self):
return "Quack"
class Robot:
def speak(self):
return "Beep boop"
class Person:
def speak(self):
return "Hello!"
def make_it_speak(thing): # works with ANY object that has speak()
print(thing.speak())
for obj in [Duck(), Robot(), Person()]:
make_it_speak(obj)
# Quack
# Beep boop
# Hello!To force every child class to implement a method, use the abc module.
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
class Rectangle(Shape):
def __init__(self, l, w):
self.l, self.w = l, w
def area(self):
return self.l * self.w
class Circle(Shape):
def __init__(self, r):
self.r = r
def area(self):
return round(3.14159 * self.r ** 2, 2)
shapes = [Rectangle(4, 5), Circle(3)]
for s in shapes:
print(type(s).__name__, s.area())
# Rectangle 20
# Circle 28.27
# Shape() -> TypeError: Can't instantiate abstract class Shape| Feature | Compile-time (static) | Run-time (dynamic) |
|---|---|---|
| Also called | Method overloading, operator overloading | Method overriding |
| Decided at | Compile time (in Java/C++) | Run time, based on the actual object |
| Requires inheritance? | No | Yes (or duck typing in Python) |
| Same method name with... | Different parameters | Same parameters in parent and child |
| In Python | Not native; simulated with default args, *args, singledispatch. Operator overloading is supported. | Fully supported — the natural form of polymorphism in Python |
class Payment:
def pay(self, amount):
raise NotImplementedError
class UPI(Payment):
def pay(self, amount):
return f"Paid Rs {amount} via UPI"
class Card(Payment):
def pay(self, amount):
return f"Paid Rs {amount} via Credit Card (2% fee: Rs {amount * 0.02:.2f})"
class Cash(Payment):
def pay(self, amount):
return f"Please pay Rs {amount} in cash on delivery"
def checkout(method, amount):
print(method.pay(amount)) # same call, different behaviour
for method in [UPI(), Card(), Cash()]:
checkout(method, 500)Paid Rs 500 via UPI
Paid Rs 500 via Credit Card (2% fee: Rs 10.00)
Please pay Rs 500 in cash on deliveryWhy this matters: Adding a new payment method (e.g. Wallet) needs zero changes to checkout(). That is the power of polymorphism: code that is open for extension.
Don't run the code yet! Write your answers in a notebook, then check the Answers section at the end of this blog.
Q1. What is the output?
print(len("hello"), len([1, 2, 3]))Q2. What is the output?
class A:
def show(self):
return "A"
class B(A):
def show(self):
return "B"
obj = B()
print(obj.show())Q3. What is the output?
class M:
def f(self):
return 1
def f(self, x):
return 2
print(M().f(5))Q4. What happens?
class M:
def f(self):
return 1
def f(self, x):
return 2
print(M().f())Q5. What is the output?
print("ab" * 2, [0] * 2, 2 * 2)Q6. Which magic method overloads the + operator?
Q7. What is the output?
class P:
def greet(self):
return "Hi"
class C(P):
def greet(self):
return super().greet() + "!"
print(C().greet())Solve these from easy to tricky:
Solutions for challenges 2 and 4 are in the Answers section.
Tick off each skill once you are confident:
Q: What is polymorphism?
The ability of one interface — a function, method or operator — to behave differently depending on the object it acts on.
Q: Does Python support method overloading?
Not natively; the last definition wins. It is simulated with default arguments, *args/**kwargs or functools.singledispatch.
Q: Difference between overloading and overriding?
Overloading: same name, different parameters, in the same class. Overriding: a child class redefines a parent method with the same signature.
Q: What is duck typing?
A style where an object's suitability is determined by the presence of methods and attributes rather than its type.
Q: What is operator overloading?
Defining how built-in operators work with user-defined classes via special methods like __add__ and __eq__.
| Concept | Syntax / Key point |
|---|---|
| Overriding | class Dog(Animal): def sound(self): ... |
| Call parent | super().method() |
| Overload + | def __add__(self, other): |
| Simulate overloading | def add(self, a, b=0, c=0): |
| Abstract method | @abstractmethod |
| Type-based dispatch | @singledispatchmethod |
# Challenge 2
class Money:
def __init__(self, amount):
self.amount = amount
def __add__(self, other):
return Money(self.amount + other.amount)
def __gt__(self, other):
return self.amount > other.amount
def __str__(self):
return f"Rs {self.amount}"
a, b = Money(500), Money(300)
print(a + b) # Rs 800
print(a > b) # True
# Challenge 4
class Calculator:
def multiply(self, a, b, c=1):
return a * b * c
calc = Calculator()
print(calc.multiply(2, 3)) # 6
print(calc.multiply(2, 3, 4)) # 24You now understand how one interface can work with many types — a key to flexible, extensible code. Next, learn Inheritance in Python, which makes run-time polymorphism possible.
If this blog helped you, watch the full video above, subscribe to the channel and share it with a friend who is learning Python. Happy coding!