Home Blogs All Courses
python c OA ( Online Assessment ) Amazon Web Services (AWS) leetcode dsa algorithms java Machine learning
Get Referral
Login Sign Up

python › Polymorphism in Python

Polymorphism in Python: Method Overloading, Overriding & Duck Typing

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.

1. What is Polymorphism?

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 typing

2. Polymorphism You Already Use Every Day

print(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 joining

Same len() function, same + operator — different behaviour for each type. That is built-in polymorphism.

3. Compile-time Polymorphism (Method Overloading)

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'

How to achieve overloading in Python

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))    # 6

Option 2 — variable arguments (*args)

class Calculator:
    def add(self, *numbers):
        return sum(numbers)

print(Calculator().add(1, 2, 3, 4, 5))   # 15

Option 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.5

4. Operator Overloading

You 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
OperatorMagic method
+__add__
-__sub__
*__mul__
/__truediv__
==__eq__
<__lt__
>__gt__
len()__len__
str() / print__str__

5. Run-time Polymorphism (Method Overriding)

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 sound

Calling the parent version with super()

class Employee:
    def salary(self):
        return 30000

class Manager(Employee):
    def salary(self):
        return super().salary() + 20000   # parent's salary + bonus

print(Manager().salary())                 # 50000

6. Duck Typing — Python's Superpower

"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!

7. Polymorphism with Abstract Base Classes

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

8. Compile-time vs Run-time Polymorphism

FeatureCompile-time (static)Run-time (dynamic)
Also calledMethod overloading, operator overloadingMethod overriding
Decided atCompile time (in Java/C++)Run time, based on the actual object
Requires inheritance?NoYes (or duck typing in Python)
Same method name with...Different parametersSame parameters in parent and child
In PythonNot native; simulated with default args, *args, singledispatch. Operator overloading is supported.Fully supported — the natural form of polymorphism in Python

9. Mini Project: Payment System

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 delivery

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

Quiz: Predict the Output

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())

Practice Challenges

Solve these from easy to tricky:

  1. Create classes Square, Triangle and Circle each with an area() method, and print the areas in a loop.
  2. Create a Money class that supports + and > between two Money objects.
  3. Create a Notification parent class and Email, SMS, Push child classes that override send().
  4. Write a multiply() method that works with 2 or 3 numbers (simulate overloading).
  5. Show duck typing with two unrelated classes that both have a fly() method.

Solutions for challenges 2 and 4 are in the Answers section.

Self-Check: Can You Do This?

Tick off each skill once you are confident:

Frequently Asked Interview Questions

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

Summary

  • Polymorphism = one name, many forms.
  • Compile-time: method overloading (simulated in Python) and operator overloading (via dunder methods).
  • Run-time: method overriding — the child's method replaces the parent's.
  • Use super() to extend the parent's method.
  • Duck typing: Python cares about behaviour, not type.
  • Abstract base classes force subclasses to implement methods.

Quick Revision Cheat Sheet

ConceptSyntax / Key point
Overridingclass Dog(Animal): def sound(self): ...
Call parentsuper().method()
Overload +def __add__(self, other):
Simulate overloadingdef add(self, a, b=0, c=0):
Abstract method@abstractmethod
Type-based dispatch@singledispatchmethod

Answers

Quiz answers

  1. 5 3
  2. B – the child's overriding method runs.
  3. 2 – the second definition replaced the first.
  4. TypeError – only f(self, x) exists now.
  5. abab [0, 0] 4 – * is polymorphic.
  6. __add__
  7. Hi!

Challenge solutions

# 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))    # 24

Conclusion

You 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!