Python Polymorphism: One Interface, Many Behaviors

Boot.dev Blog » Python » Python Polymorphism: One Interface, Many Behaviors
Lane Wagner
Lane Wagner Boot.dev co-founder and backend engineer

Last published March 20, 2026

Table of Contents

Polymorphism is where OOP starts to feel truly powerful. You stop writing giant if type == ... trees and start trusting shared interfaces. Different objects respond to the same method call in different ways, and your calling code stays clean.

All the content from our Boot.dev courses are available for free here on the blog. This one is the “Polymorphism” chapter of Learn Object Oriented Programming in Python. If you want to try the far more immersive version of the course, do check it out!

What Is Polymorphism in Python?

Polymorphism means one interface, many implementations. In practice, classes share method names and signatures, but each class does its own thing.

class Creature:
    def move(self):
        return "the creature moves"


class Dragon(Creature):
    def move(self):
        return "the dragon flies"


class Kraken(Creature):
    def move(self):
        return "the kraken swims"


creatures = [Creature(), Dragon(), Kraken()]
print([creature.move() for creature in creatures])
# ['the creature moves', 'the dragon flies', 'the kraken swims']

Same call (move()), different behavior per object. That’s the whole deal.

If classes and methods still feel shaky, warm up with Python classes and objects first.

How Does Duck Typing Actually Work?

Duck typing means Python cares more about behavior than ancestry. If an object has the method your code needs, you’re good to call it.

class Griffin:
    def move(self):
        return "the griffin glides"


class Shark:
    def move(self):
        return "the shark swims"


def describe_movement(creature):
    return creature.move()


print(describe_movement(Griffin()))  # the griffin glides
print(describe_movement(Shark()))  # the shark swims

Griffin and Shark don’t share a parent class here. It still works because they both satisfy the expected interface.

That flexibility is amazing, but it also means naming and consistency matter even more. Keep your interfaces tight and predictable, just like in Python clean code.

Why Do Shared Method Signatures Matter?

Polymorphism breaks down when signatures drift. If one class expects extra parameters and another doesn’t, caller code gets messy fast.

class Human:
    def hit_by_fire(self):
        return "human loses 5 health"


class Archer:
    def hit_by_fire(self):
        return "archer loses 10 health"


targets = [Human(), Archer()]
print([target.hit_by_fire() for target in targets])
# ['human loses 5 health', 'archer loses 10 health']

Both methods use the same shape, so callers don’t care which concrete type they’re holding.

How Is Polymorphism Used With Hitboxes?

A great real example from the course: a generic Unit checks area membership by center point, while Dragon overrides that method to use hitbox overlap.

class Unit:
    def __init__(self, name, pos_x, pos_y):
        self.name = name
        self.pos_x = pos_x
        self.pos_y = pos_y

    def in_area(self, x1, y1, x2, y2):
        return x1 <= self.pos_x <= x2 and y1 <= self.pos_y <= y2


class Dragon(Unit):
    def __init__(self, name, pos_x, pos_y, width, height):
        super().__init__(name, pos_x, pos_y)
        self.width = width
        self.height = height

    def in_area(self, x1, y1, x2, y2):
        left = self.pos_x - self.width / 2
        right = self.pos_x + self.width / 2
        bottom = self.pos_y - self.height / 2
        top = self.pos_y + self.height / 2
        return not (right < x1 or left > x2 or top < y1 or bottom > y2)


units = [Unit("Scout", 3, 3), Dragon("Nidhogg", 3, 3, 4, 2)]
print([unit.in_area(4, 4, 5, 5) for unit in units])  # [False, True]

Caller code doesn’t change. It still calls in_area(...) on each unit. That’s polymorphism doing real work.

What Is Operator Overloading in Python?

Operator overloading is built-in polymorphism with special methods. You define behavior for operators like +, ==, and < on your own classes.

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 __str__(self):
        return f"({self.x}, {self.y})"


spawn = Point(4, 5)
offset = Point(2, 3)
print(spawn + offset)  # (6, 8)

This works because Python maps + to __add__ under the hood.

Custom string output works the same way through __str__.

How Do Comparison Operators Make Domain Models Better?

For domain objects like cards, comparisons read much better when encoded directly into the class.

class Card:
    ranks = ["2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King", "Ace"]
    suits = ["Clubs", "Diamonds", "Hearts", "Spades"]

    def __init__(self, rank, suit):
        self.rank = rank
        self.suit = suit
        self.rank_index = Card.ranks.index(rank)
        self.suit_index = Card.suits.index(suit)

    def __gt__(self, other):
        if self.rank_index != other.rank_index:
            return self.rank_index > other.rank_index
        return self.suit_index > other.suit_index


ace_hearts = Card("Ace", "Hearts")
king_spades = Card("King", "Spades")
print(ace_hearts > king_spades)  # True

Now game logic can compare cards naturally, and it stays easy to read.

When Should You Avoid Clever Polymorphism?

Polymorphism should reduce complexity, not hide it.

Skip fancy abstractions when:

  • Shared interfaces are unstable
  • Behavior is surprising to callers
  • Overloaded operators don’t match intuition

If your + operator mutates objects in-place or your __lt__ method compares unrelated things, readers will hate you. Keep behavior unsurprising.

This is also where Learn Functional Programming in Python helps. Pure functions and explicit transforms are often clearer than over-engineered class hierarchies.

What Should You Learn After Python Polymorphism?

After this chapter, you’ve got the core OOP toolbox: classes, encapsulation, abstraction, inheritance, and polymorphism. Next step is practice in real projects where design tradeoffs are messy and constraints are real.

Finish Learn Object Oriented Programming in Python, then apply it in backend work on the Back-end Developer Path in Python and Go. That’s where these concepts stop feeling academic and start paying rent.

Frequently Asked Questions

What is polymorphism in Python?

Polymorphism means different objects can share the same method names while providing their own implementations.


Is polymorphism only for inheritance?

No. Inheritance is common, but duck typing and operator overloading are also polymorphism in Python.


What is duck typing in Python?

Duck typing means you can use an object based on what methods it has, not strictly on its class type.


What is operator overloading in Python?

Operator overloading lets classes define how operators like plus, less than, and equality behave by implementing special methods.


When should I avoid custom operator overloading?

Avoid it when the behavior is surprising or unclear. Overloads should feel natural and predictable to readers.

Related Articles