Polymorphism
Polymorphism means "many forms." In programming, it describes the ability to call the same method name on different objects and have each object respond in its own way. You write code that says "do your thing" without caring exactly what kind of object it's talking to, and each object decides for itself what "doing your thing" actually means.
Polymorphism is one of the four pillars of object-oriented programming, alongside encapsulation, inheritance, and abstraction. Python supports it in two distinct flavors: the classic, inheritance-based version you'll recognize from languages like Java, and a much more flexible, dynamic version unique to Python's design called duck typing. This page covers both.
Polymorphism Through Method Overriding
The most common way to see polymorphism is through method overriding, which relies on inheritance. A quick recap: when a child class defines a method with the same name as one in its parent class, the child's version takes over for instances of that child. Different subclasses can override the same parent method differently, so calling that one method name produces different behavior depending on which subclass the object actually is.
class Animal:
def make_sound(self):
return "Some generic animal sound"
class Dog(Animal):
def make_sound(self):
return "Woof!"
class Cat(Animal):
def make_sound(self):
return "Meow!"
animals = [Animal(), Dog(), Cat()]
for animal in animals:
print(animal.make_sound())Some generic animal sound Woof! Meow!
The loop calls .make_sound() the exact same way on every object, but the actual code that runs depends on each object's real class. That's polymorphism: one interface, many implementations. Every subclass here shares a common ancestor, Animal, which is what makes this the "classic," inheritance-based style.
Duck Typing: Python's Other Kind of Polymorphism
Python takes polymorphism further with a philosophy known as duck typing, summarized by the saying: "if it walks like a duck and quacks like a duck, it's a duck." In practice, this means Python doesn't check what class an object belongs to before letting you call a method on it. It only checks whether the object has the method or attribute you're trying to use, at the moment you try to use it.
This is a much looser, more dynamic form of polymorphism than method overriding. There's no shared base class required, no isinstance checks, and no formal contract. Any object with the right method just works, regardless of its ancestry.
Worked Example: No Shared Base Class Required
Here are three completely unrelated classes: Duck, Person, and Robot. They don't inherit from each other or from any common parent, but each one defines a make_sound method:
class Duck:
def make_sound(self):
print("Quack!")
class Person:
def make_sound(self):
print("Hello there!")
class Robot:
def make_sound(self):
print("BEEP BOOP.")
def make_it_speak(thing):
thing.make_sound()
make_it_speak(Duck())
make_it_speak(Person())
make_it_speak(Robot())Quack! Hello there! BEEP BOOP.
The make_it_speak function never checks what type thing is. It just assumes thing has a make_sound method and calls it. Because Duck, Person, and Robot all happen to provide that method, all three work perfectly fine as arguments — even though none of them share a common ancestor. This is polymorphism achieved purely through shared behavior, not shared class hierarchy.
Inheritance-based polymorphism: subclasses override a method defined by a shared parent class.
Duck typing: any object with the right method or attribute works, no shared parent required.
Both let you write one function or loop that treats different objects uniformly, while each object supplies its own behavior.
A Teaser: Operator Overloading
There's a third face of polymorphism you've actually been using all along without thinking about it: operators. The + operator behaves completely differently depending on what you hand it:
print(1 + 2) # adds numbers
print("a" + "b") # concatenates strings
print([1, 2] + [3, 4]) # concatenates lists3 ab [1, 2, 3, 4]
Same symbol, three different behaviors, chosen based on the type of the operands — that's polymorphism baked directly into Python's syntax. What's more, Python lets you plug your own classes into this system: by defining special methods like __add__ on a class, you can make + (and ==, len(), and many other built-in operators and functions) work naturally with objects you write yourself. That mechanism, and the full family of these special methods, is covered in depth on the next page, "Dunder / Magic Methods."