100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Python

Polymorphism in Python

How different objects can respond to the same method call or operator in their own way, enabled by duck typing.

OOP BasicsIntermediate9 min readJul 7, 2026
Analogies

1. Introduction

Polymorphism (from Greek, "many forms") means that the same operation or method call can behave differently depending on the object it is applied to. In Python, this shows up when different classes implement a method with the same name, and calling that method on each object produces behavior specific to that object's class.

🏏

Cricket analogy: Polymorphism is like the same call of 'play a cover drive' behaving differently depending on whether Virat Kohli or Steve Smith executes it -- each player's class implements the shot with the same name but their own technique.

Polymorphism lets you write generic code that works with any object exposing the expected interface, without needing to know the object's exact type in advance — a cornerstone of flexible, reusable object-oriented design.

🏏

Cricket analogy: Polymorphism lets a commentator say 'the bowler took a wicket' generically, without needing to know in advance whether it's a pacer's yorker or a spinner's googly -- any object exposing a 'take_wicket' interface works.

2. Syntax

python
class Shape:
    def area(self):
        raise NotImplementedError


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

    def area(self):
        return 3.14159 * self.radius ** 2


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

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


for shape in [Circle(2), Square(3)]:
    print(shape.area())   # same call, different behavior

3. Explanation

In the example, both Circle and Square define an area() method, but each computes it differently. Calling shape.area() inside the loop works identically regardless of which subclass shape refers to — Python looks up area on the actual object's class at runtime and calls the matching implementation. This is sometimes called runtime (or dynamic) polymorphism.

🏏

Cricket analogy: Both a 'PaceBowler' and a 'SpinBowler' class define a deliver() method, but each computes the ball's trajectory differently; calling bowler.deliver() in the over-loop works identically regardless of which subclass is bowling -- runtime polymorphism resolved at call time.

Python also achieves polymorphism through operator overloading (e.g., + works differently for int, str, and list via each type's __add__), and through built-in functions like len() working on strings, lists, and dicts alike because each defines __len__.

🏏

Cricket analogy: Python's + working differently for int, str, and list mirrors how a scoreboard's 'total' operation works differently for runs, player names, and squad lists, each type defining its own __add__; similarly, len() works on a playing XI list because it defines __len__.

Python's polymorphism is largely driven by duck typing: "If it walks like a duck and quacks like a duck, it's treated as a duck." Python does not require objects to share a common base class to be used polymorphically — it only checks, at call time, whether the object has the method being called. This is different from strictly typed languages that require formal interface implementation.

Because Python is dynamically typed, calling a method that an object doesn't implement raises an AttributeError only at runtime, not at compile time. If Shape.area() is meant to be a required contract, using an Abstract Base Class (ABC) with @abstractmethod (from the abc module) is safer than just raise NotImplementedError, since ABCs prevent instantiating an incomplete subclass at all.

4. Example

python
class Shape:
    def area(self):
        raise NotImplementedError("Subclasses must implement area()")


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

    def area(self):
        return round(3.14159 * self.radius ** 2, 2)


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

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


class Duck:
    def area(self):
        return "Ducks don't have area, but they can quack!"


shapes = [Circle(2), Square(3), Duck()]
for shape in shapes:
    print(f"{type(shape).__name__}: {shape.area()}")

5. Output

text
Circle: 12.57
Square: 9
Duck: Ducks don't have area, but they can quack!

6. Key Takeaways

  • Polymorphism lets the same method name behave differently depending on the object's class.
  • Python resolves which method implementation to run at runtime, based on the object's actual type.
  • Duck typing means Python cares whether an object supports a method, not its declared type or base class.
  • Operator overloading (e.g., __add__, __len__) is a form of built-in polymorphism.
  • For enforced contracts, prefer Abstract Base Classes over relying solely on NotImplementedError.

Practice what you learned

Was this page helpful?

Topics covered

#Python#PythonProgrammingStudyNotes#Programming#PolymorphismInPython#Polymorphism#Syntax#Explanation#Example#OOP#StudyNotes#SkillVeris