Polymorphism
Learn how the same method name can behave differently across classes through method overriding, duck typing, and operator overloading.
Introduction
The word "polymorphism" comes from Greek roots meaning "many forms." In programming, it describes a simple but powerful idea: the same method name can behave differently depending on which object it is called on. You already saw a taste of this in the previous lesson with method overriding — this lesson explores the concept fully.
You will learn how Python achieves polymorphism through method overriding, how "duck typing" lets objects be used interchangeably without a shared base class, and get a brief preview of operator overloading, which the next lesson covers in full.
- What polymorphism means: same method name, different behavior.
- How method overriding is the main mechanism for polymorphism in Python.
- What duck typing is and how Python embraces it.
- How to write code that treats different objects the same way through a common method name.
- A brief look at operator overloading (e.g. __add__).
What is Polymorphism?
Polymorphism means that different classes can define a method with the same name, and calling that method on an object automatically runs the version that belongs to that object's class. The caller does not need to know or care exactly which class it is dealing with — it just calls the method and gets the appropriate behavior.
Same Name
Multiple classes define a method using the exact same method name.
Different Behavior
Each class's version of the method does something specific to that class.
Uniform Calling Code
Code that calls the method does not need special cases for each class.
Polymorphism Through Method Overriding
The most direct way Python achieves polymorphism is through method overriding, which you learned in the Inheritance lesson: several subclasses share a common parent and each overrides the same method name with its own version.
class Shape:
def area(self):
return 0
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.1416 * self.radius ** 2
class Square(Shape):
def __init__(self, side):
self.side = side
def area(self):
return self.side ** 2
shapes = [Circle(5), Square(4)]
for shape in shapes:
print(f"{shape.__class__.__name__} area: {shape.area()}")Circle area: 78.54
Square area: 16Notice that the loop calls shape.area() the exact same way for every object, yet each one runs its own class's version. This is polymorphism in action — a single loop that works correctly no matter how many shape types you add in the future.
Duck Typing
Python takes polymorphism a step further with an idea called duck typing, based on the saying: "If it walks like a duck and quacks like a duck, it probably is a duck." In practice, this means Python does not require objects to share a common parent class at all — it only cares whether the object has the method being called.
class Duck:
def sound(self):
print("Quack!")
class Dog:
def sound(self):
print("Woof!")
class Car:
def sound(self):
print("Vroom!")
# None of these classes inherit from a shared parent!
for thing in [Duck(), Dog(), Car()]:
thing.sound()Quack!
Woof!
Vroom!Duck, Dog, and Car have completely unrelated class hierarchies. Python does not check "is this object a Duck?" — it only checks "does this object have a sound() method?" That flexibility is what makes duck typing so powerful in Python.
Polymorphism with Built-In Functions
You have actually been using polymorphism since your very first Python program, without necessarily naming it. Built-in functions like len() and operators like + behave differently depending on the type of object they receive.
print(len("Hello")) # length of a string
print(len([1, 2, 3, 4])) # length of a list
print(len({"a": 1, "b": 2})) # number of keys in a dict
print(1 + 2) # numeric addition
print("Py" + "thon") # string concatenation
print([1, 2] + [3, 4]) # list concatenation5
4
2
3
Python
[1, 2, 3, 4]The same len() call and the same + operator produce sensible, type-appropriate behavior for strings, lists, and dictionaries. This works because these types each implement the underlying methods (like __len__ and __add__) that len() and + rely on — the exact mechanism covered in the next lesson.
A Brief Look at Operator Overloading
Operator overloading is a related concept: defining what an operator like + means for your own custom class. Python does this through special methods (called "magic" or "dunder" methods), such as __add__ for the + operator. The next lesson explores dunder methods in full detail — for now, here is a quick preview.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Point(self.x + other.x, self.y + other.y)
def __repr__(self):
return f"Point({self.x}, {self.y})"
p1 = Point(1, 2)
p2 = Point(3, 4)
print(p1 + p2) # uses our custom __add__Point(4, 6)By defining __add__, the + operator now works with Point objects just like it works with numbers or strings — another form of polymorphism, since the same + symbol behaves differently depending on the operand types.
Why Polymorphism Matters
Cleaner Code
No need for long if/elif chains checking an object's exact type before calling a method.
Easy to Extend
Adding a new class that implements the expected method(s) just works with existing code.
Flexible Interfaces
Functions can accept "anything that behaves the right way" instead of one specific type.
Common Mistakes
- Writing long isinstance() or type() checks instead of relying on polymorphism and duck typing.
- Assuming an object must inherit from a specific base class before its methods can be called polymorphically — duck typing rarely requires this.
- Forgetting to override every relevant method in a subclass, causing it to silently use inappropriate parent behavior.
- Confusing polymorphism (same name, different behavior) with simply overloading a function name — Python does not support traditional function overloading by parameter type.
Best Practices
- Design classes around a shared method name (like area() or sound()) so they can be used polymorphically.
- Lean on duck typing — check "does it have the method I need?" rather than "what exact type is it?".
- Keep overridden methods focused on doing one class-specific job well.
- Use operator overloading sparingly and only when it makes a custom class genuinely more intuitive to use.
- Document what methods a class is expected to implement if you are relying on duck typing across a codebase.
Frequently Asked Questions
Is polymorphism the same as method overriding?
Method overriding is the main mechanism that enables polymorphism in Python, but polymorphism is the broader concept — it also includes duck typing and operator overloading, where a shared base class is not even required.
What is duck typing in simple terms?
It means Python cares about whether an object has the methods you are trying to call, not what class it officially belongs to. If it has the right method, it can be used.
Does Python support traditional method overloading (same name, different parameters)?
Not in the way Java or C++ do. Python only keeps the last-defined version of a method with a given name. Similar effects are achieved using default arguments or *args/**kwargs instead.
Why did the Point example print nicely with __add__ but not with plain print(p1)?
Because we also defined __repr__, which controls how an object is displayed. Without it, printing a Point would show a generic memory address instead. The next lesson covers __repr__ and __str__ in depth.
Do I need inheritance to use polymorphism?
No. While polymorphism often appears alongside inheritance (subclasses overriding a parent's method), duck typing shows that completely unrelated classes can be used polymorphically too.
Key Takeaways
- Polymorphism means the same method name behaves differently depending on the object calling it.
- Method overriding, learned in the Inheritance lesson, is the primary mechanism for polymorphism in Python.
- Duck typing means Python checks whether an object has the right method, not whether it shares a specific parent class.
- Built-in functions like len() and operators like + are already polymorphic across different types.
- Operator overloading lets custom classes define their own behavior for operators like + through methods such as __add__.
Summary
Polymorphism allows the same method name or operator to behave appropriately across different types of objects, whether through method overriding in a class hierarchy or through duck typing across unrelated classes. This flexibility keeps calling code simple and makes it easy to extend a program with new types.
Next, you will learn about abstraction — how to hide complex implementation details behind a simple, well-defined interface using Python's abc module.
- You understand what polymorphism means.
- You can use method overriding to achieve polymorphic behavior.
- You understand duck typing and why Python does not require a shared base class.
- You have seen a preview of operator overloading with __add__.
- You are ready to learn about abstraction.