What is duck typing, and how does `typing.Protocol` formalize it?

5 minintermediatefundamentalstypingduck-typing

Quick Answer

Duck typing means Python checks behavior, not declared type — "if it walks like a duck and quacks like a duck, it's a duck." Any object with the right methods/attributes works, regardless of its class hierarchy. typing.Protocol (PEP 544) lets you describe that structurally for static type checkers. A class satisfies a Protocol by having matching methods, with no explicit inheritance required.

Detailed Answer

Duck typing in practice

class Duck:
    def quack(self):
        return "Quack!"

class Person:
    def quack(self):
        return "I'm quacking!"

def make_it_quack(thing):
    return thing.quack()   # no type check — just calls the method

make_it_quack(Duck())     # works
make_it_quack(Person())   # also works — Person isn't a Duck subclass

make_it_quack never checks isinstance(thing, Duck). It just calls .quack() and trusts that the object supports it. This is why for x in anything: works on lists, files, generators, and custom classes alike: they all implement __iter__, regardless of ancestry.

The problem for static typing

Duck typing is dynamic — you find out at runtime whether an object has the right method. Static type checkers (mypy, pyright) need something they can verify ahead of time without requiring every duck-typed class to inherit from a common base, which would defeat the point.

Protocol: structural typing for static checkers

from typing import Protocol

class Quacks(Protocol):
    def quack(self) -> str: ...

def make_it_quack(thing: Quacks) -> str:
    return thing.quack()

make_it_quack(Duck())     # type-checks fine — Duck has a matching quack()
make_it_quack(Person())   # also fine — no inheritance from Quacks needed

Quacks is never inherited from. mypy checks whether Duck and Person structurally match its method signatures. This is "nominal vs structural" typing: ABC/inheritance is nominal (you must declare the relationship), Protocol is structural (the shape is enough).

When to use which

  • Plain duck typing: quick scripts, internal code, when you don't need static verification.
  • Protocol: public APIs and larger codebases where you want mypy to catch "this object doesn't have the method you're calling" before runtime, without forcing every caller into a shared base class.

Duck typing lets Python code work with any object that has the right methods, independent of its class hierarchy. Protocol gives that the same runtime flexibility plus static verification. A class satisfies a Protocol by shape, not by declared inheritance.