Free demoJob-oriented courses at Debugshala, Old Palasia, Indore. Freshers, non-IT and year-gap welcome.Book a free demo +91-8982385539

Python Polymorphism: Duck Typing, Method Overriding and ABCs

Lesson 41 of 52 · 8 min · Updated 13 Nov 2026

Learn Python polymorphism: one interface for many classes, method overriding, duck typing, abstract base classes, Protocol and singledispatch, with examples.

Look at the plug point on the wall of any office in Indore. You can plug in a kettle, a laptop charger, a desk fan or a phone charger. The socket does not know or care what is on the other end. It offers one interface, two pins and 230 volts, and each device does its own thing with it.

The kettle heats water. The charger fills a battery. The fan spins. If the office buys a new air purifier next month, nobody rewires the socket. The purifier just needs the right plug.

That is polymorphism. The socket is your calling code. The plug shape is the interface, such as a method called export(). Each device is a different class with its own implementation of that method.

Polymorphism in Python is the ability of different classes to respond to the same method call or operation in their own way, so one piece of code can work with many types of object without checking which type it has.

This lesson builds on Python inheritance, where you overrode parent methods. Examples target Python 3.12+.

What is polymorphism in Python?

You have relied on polymorphism since your first loop. Built-in functions and operators already behave differently depending on the type they receive.

Python
items = ["Indore", [1450, 980, 2300], {"city": "Ujjain", "orders": 41}, (3, 5)]
for item in items:
    print(f"len({item!r}) = {len(item)}")

print(1450 + 980)               # numbers add
print("Old " + "Palasia")       # strings join
print([1, 2] + [3])             # lists concatenate
print(sorted("rajwada"), sorted([3, 1, 2]))

len() does not contain separate code for strings, lists and dicts. It asks the object for its length, and each type answers in its own way. Your classes can join in. How + and len() hook into your own classes is the subject of Python operator overloading.

Polymorphism with method overriding

The classic form uses a parent class with a method and children that override it. The calling code talks only to the shared method name.

Python
import json

class Exporter:
    def export(self, rows):
        raise NotImplementedError

class CsvExporter(Exporter):
    def export(self, rows):
        lines = [",".join(rows[0])] + [",".join(str(v) for v in r.values()) for r in rows]
        return "\n".join(lines)

class JsonExporter(Exporter):
    def export(self, rows):
        return json.dumps(rows)

class MarkdownExporter(Exporter):
    def export(self, rows):
        line = lambda cells: "| " + " | ".join(map(str, cells)) + " |"
        rule = "|" + "---|" * len(rows[0])
        return "\n".join([line(rows[0]), rule] + [line(r.values()) for r in rows])

rows = [{"city": "Indore", "orders": 412}, {"city": "Bhopal", "orders": 298}]
for exporter in [CsvExporter(), JsonExporter(), MarkdownExporter()]:
    print(f"--- {type(exporter).__name__}")
    print(exporter.export(rows))

The loop never asks "is this a CSV exporter?". It calls export() and trusts each object. Adding a fourth format later means writing one new class; the loop does not change. The JSON format itself is covered in Python JSON.

Python polymorphism: one call exporter.export(rows) dispatches to CsvExporter, JsonExporter or MarkdownExporter depending on the object

The calling code stays the same; the object decides which implementation runs.

What is duck typing in Python?

Python does not actually require a shared parent. If an object has the method you call, it works. The phrase comes from "if it walks like a duck and quacks like a duck, it is a duck". Python checks what an object can do, not what it is.

Python
import io

class FakeUpiFeed:
    """Not a file, but it has a read() method."""
    def read(self):
        return "450\n1200\n799\n"

def total_amount(source):
    return sum(float(line) for line in source.read().split())

print(total_amount(io.StringIO("1450\n980\n2300\n")))   # an in-memory file
print(total_amount(FakeUpiFeed()))                      # a custom class

total_amount works with a real file object, an io.StringIO buffer or a test double, because all three have read(). That is why duck typing is so useful for testing data code: you can feed a function fake inputs without touching disk. Real file objects were covered in Python file handling.

If an object lacks the method, you get an AttributeError at the moment of the call. In keeping with Python's "ask forgiveness" style, you usually let that error surface rather than checking hasattr() in advance.

Quick check

What does duck typing mean in Python?

Abstract base classes: enforcing the interface

Duck typing is flexible, but it fails late. If someone writes an exporter and misspells export as exprot, nothing complains until the export runs. An abstract base class (ABC) catches this earlier, when the object is created.

Python
from abc import ABC, abstractmethod

class Exporter(ABC):
    @abstractmethod
    def export(self, rows):
        """Return the rows as text."""

    def save_note(self, rows):                       # concrete, shared method
        return f"{type(self).__name__} exported {len(rows)} rows"

class CsvExporter(Exporter):
    def export(self, rows):
        return "\n".join(",".join(map(str, r.values())) for r in rows)

class BrokenExporter(Exporter):
    def exprot(self, rows):                          # typo: export is missing
        return ""

print(CsvExporter().save_note([{"a": 1}, {"a": 2}]))
for cls in (Exporter, BrokenExporter):
    try:
        cls()
    except TypeError:
        print(f"{cls.__name__}: cannot be created, export() not implemented")

An ABC can mix abstract methods, which subclasses must provide, with normal methods they inherit for free. You cannot create an instance of the ABC itself or of any subclass that still has an abstract method. The full rules are in the abc module documentation.

Python abstract base class: Exporter ABC requires export(); CsvExporter implements it and can be created, BrokenExporter does not and raises TypeError

An ABC turns a forgotten method into an immediate TypeError instead of a late surprise.

Protocols: duck typing for type checkers

typing.Protocol describes an interface without inheritance. Any class with matching methods counts, and tools like mypy check it before the code runs. It suits codebases that already use Python type hints.

Python
from typing import Protocol

class Readable(Protocol):
    def read(self) -> str: ...

class SmsFeed:                       # does not inherit from Readable
    def read(self) -> str:
        return "1200 799"

def count_values(src: Readable) -> int:
    return len(src.read().split())

print(count_values(SmsFeed()))

Quick check

A subclass of an ABC does not implement one @abstractmethod. When does the error appear?

Ways to get polymorphism in Python

Approach Needs a common parent? Checked when? Best for
Method overriding Yes At call time Classes that share real code
Duck typing No At call time Small scripts, test doubles
abc.ABC Yes When the object is created Plugin families that must follow a contract
typing.Protocol No By a type checker, before running Typed codebases, third-party classes
functools.singledispatch No At call time, by argument type One function, different behaviour per type

Does Python support function overloading?

Not in the Java or C++ sense. If you define two functions with the same name, the second simply replaces the first. Use default arguments for optional parameters, or functools.singledispatch when behaviour depends on the type of the first argument.

Python
from datetime import date
from functools import singledispatch

@singledispatch
def fmt(value):
    return str(value)

@fmt.register
def _(value: float):
    return f"{value:,.2f}"

@fmt.register
def _(value: date):
    return value.strftime("%d %b %Y")

for v in [48200.5, date(2026, 11, 12), "Indore", 7]:
    print(type(v).__name__, "->", fmt(v))

singledispatch picks the registered function that matches the type of the first argument, falling back to the original for anything unregistered.

Common mistakes with polymorphism

  • Type checks instead of methods. A chain of if isinstance(x, CsvExporter) ... elif ... defeats the purpose. Put the behaviour in each class.
  • Inconsistent signatures. If one export(rows) takes a list and another needs (rows, path), callers break. Keep overrides compatible.
  • Returning different kinds of result. One exporter returning text and another writing a file is confusing. Agree on one contract.
  • Using ABCs everywhere. For a two-class script, duck typing is simpler.
  • Defining a function twice expecting overloading. The later definition wins silently.

Key takeaways

  • Polymorphism lets one call work with many types, each in its own way.
  • Built-ins like len() and + are already polymorphic.
  • Overriding gives polymorphism through inheritance; duck typing gives it without.
  • ABCs enforce that subclasses implement required methods.
  • typing.Protocol checks interfaces statically without inheritance.
  • Python has no signature overloading; use defaults or singledispatch.

Next, make +, == and len() work on your own classes in Python operator overloading. Designing exporters and report plugins like these is the kind of exercise you build in Debugshala's data analytics classes for career switchers.

Take the free Python Programming quiz10 quick questions with explanations. No sign-up needed, and you can come back to this lesson after.

FAQ

What is polymorphism in Python with an example?

Polymorphism is when the same operation works on different types, each in its own way. len("Indore") counts characters while len([1, 2, 3]) counts items. With your own classes, a CsvExporter and a JsonExporter can both have an export() method, and a loop can call it without knowing which one it holds.

What is the difference between overloading and overriding in Python?

Overriding means a subclass redefines a method it inherited, and Python fully supports it. Overloading means several functions with the same name but different parameters. Python does not support that directly; a second definition replaces the first. You get similar results with default arguments, *args, or functools.singledispatch.

Is duck typing the same as polymorphism?

Duck typing is one way Python achieves polymorphism. Classical polymorphism relies on a shared parent class and overridden methods. Duck typing drops the parent requirement: any object with the right methods works. Most Python code uses duck typing, with ABCs or Protocols added when an explicit contract helps.

When should I use an abstract base class in Python?

Use an ABC when several classes must follow the same contract and a missing method would cause a hard-to-find bug, such as a family of exporters or data source plugins. The ABC raises TypeError the moment someone creates an incomplete subclass object, which is far earlier than a failing call.

How do I prepare OOP questions for analyst interviews?

Expect to explain polymorphism, overriding versus overloading and duck typing, often with a short code sample. Practise writing two or three classes with a shared method, then try OOP and polymorphism questions on Debugshala Test. Debugshala in Old Palasia, Indore, also offers a free demo class for its analytics programme.

From the team behind this site

Go further with the Data Analytics Course at Debugshala

These tutorials are written by Debugshala, a training and placement institute at Old Palasia, Indore. The classroom course adds live mentoring, real projects and interview preparation.

  • Offline classes at Old Palasia, Indore
  • Free demo class before you pay
  • Non-IT, fresher and year-gap friendly
  • Placement assistance

700+ students placed · 200+ hiring partners · 4.9★ Google rating

Python Inheritance: Parent and Child Classes, super() and MROPython Operator Overloading: Magic Methods __add__ and __eq__