Start learning free

LESSON 184 OF 344 · OBJECT-ORIENTED PROGRAMMING

Polymorphism in Python: One Call, Many Behaviors

Lesson Read the full explanation

TL ; DR:

Polymorphism is when a single piece of code (like animal.speak()) does different things depending on which subclass the object actually is. You write the call once, and the right override runs automatically.


animals = [Dog(), Cat()]
for a in animals:
    a.speak()    # dispatches to Dog.speak then Cat.speak


Full lesson:

Polymorphism (Greek for "many forms") is what makes inheritance and interfaces actually useful at scale. Once Dog and Cat both inherit from Animal and override speak(), you can stop caring which one you're holding. Any code that takes an Animal and calls speak() works for both.

Why it matters

Imagine you're writing a function that prints what every animal in a zoo says:

void Roll(Animal[] animals) {
    foreach (var a in animals) a.Speak();
}

This function works for any current or future kind of animal. Add a Cow subclass tomorrow — this loop runs Cow.Speak() automatically, no edits needed. That's the win: code that works today and keeps working as the type hierarchy grows.

Static type vs dynamic type

The reference is typed as Animal. The object it actually points to is a Dog, or a Cat, etc. Polymorphism is the language dispatching the method call based on the dynamic type (what it is at runtime) rather than the static type (what the variable was declared as).

Notes by language

  • C# / C++ — the parent method must be virtual (and the override marked override) for runtime dispatch. Otherwise you get static dispatch and the wrong version runs.
  • Java / JS / Python — methods are virtual by default. Just override and it works.

Your task

Animal, Dog, and Cat are already defined, with the right overrides. Inside MainFunction, build a collection containing one Dog and one Cat (typed as Animal), then loop through it calling speak() on each.

Expected output:

Woof
Meow
STEP 1 OF 2
    Ctrl + Enter to check

    PYTHON · OBJECT-ORIENTED PROGRAMMING

    Polymorphism in Python, explained

    What polymorphism is

    Polymorphism, Greek for "many forms", means that one line of code can do different things depending on the object it is given. You write animal.speak() once, and the version of speak that runs is the one belonging to that particular animal. It is what turns inheritance and overriding into something practical: once Dog and Cat both override speak(), code that deals with animals no longer needs to know which kind it holds.

    An example

    class Animal:
        def speak(self):
            print("...")
    
    class Dog(Animal):
        def speak(self):
            print("Woof")
    
    class Cat(Animal):
        def speak(self):
            print("Meow")
    
    animals = [Dog(), Cat()]
    for a in animals:
        a.speak()
    
    Woof
    Meow
    

    The loop contains one call, a.speak(), yet the output differs on each pass. Python looks up speak on the actual object at the moment of the call, so the dog's override runs for the dog and the cat's for the cat.

    Why it helps

    A loop like this works for kinds of animals that do not exist yet. If you add a Cow class tomorrow with its own speak, the same loop prints "Moo" for it with no edit. Code that works today and keeps working as the family of classes grows is the point of polymorphism. The alternative is a chain of if checks on the kind of animal, which must be extended every time a class is added.

    Python's version: the type of the object decides

    In languages with declared types, a variable has a static type (what it was declared as) and the object has a dynamic type (what it is at run time). Polymorphism means the call follows the dynamic type. Python variables have no declared type at all, so only the object's actual class matters.

    One consequence is that Python does not need a shared parent for this to work. Any object that has a suitable method can be used:

    class Robot:
        def speak(self):
            print("Beep")
    
    for a in [Dog(), Robot()]:
        a.speak()
    
    Woof
    Beep
    

    Robot is not an Animal, but it has speak(), and that is all the loop needs. This style is often called duck typing: if the object behaves the way the code requires, it is accepted. Inheritance remains a clear way to document the relationship, but Python does not demand it. The failure case is also clear: an object without the method, such as one of a class that has no speak, raises an AttributeError when the loop reaches it.

    Built-in functions use the same idea. len works on strings, lists and dictionaries, and each type provides its own way to report its size.

    The lesson's exercise

    The starter code defines Animal, Dog and Cat with the overrides already in place. The task is to use them polymorphically at module level, below the classes.

    1. Build the list. Write animals = [Dog(), Cat()]. A list can hold objects of different classes, and both are animals, so later code can treat them alike.
    2. Loop and call. Write for a in animals: and, indented inside the loop, a.speak(). The first pass prints Woof and the second Meow, with no check of the kind.
    class Animal:
        def speak(self):
            print("...")
    
    class Dog(Animal):
        def speak(self):
            print("Woof")
    
    class Cat(Animal):
        def speak(self):
            print("Meow")
    
    animals = [Dog(), Cat()]
    for a in animals:
        a.speak()
    
    Woof
    Meow
    

    Common mistakes

    • Forgetting the parentheses when building the list. [Dog, Cat] holds the classes themselves, and Dog.speak() then fails because no object supplies self.
    • Calling speak outside the loop. The call must be indented inside the for, or it runs once, after the loop, on the last animal only.
    • Checking types by hand. Writing if isinstance(a, Dog): ... for each kind defeats the purpose. Let the override choose.
    • A subclass that forgets to override. It inherits the parent's version, so a class that does not define speak prints the parent's ....

    Key takeaways

    • Polymorphism means one call, such as a.speak(), runs different code depending on the object.
    • Python decides at the moment of the call, from the actual object's class.
    • Methods are overridable by default in Python, so no special keyword is involved.
    • Objects of unrelated classes work together when they provide the method that the code calls.
    Keep going with PythonAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free