Skip to the content.

Protocol: The Power of Structural Typing

In large enterprise systems, traditional inheritance-based interfaces (ABCs) create rigid, brittle hierarchies. If you want to use a module that wasn’t built for your system, you are forced to refactor it to inherit from your base classes. This tight coupling is a primary driver of the “midnight deployment spike”—unforeseen side effects caused by deep inheritance chains.

typing.Protocol solves this by implementing Structural Typing (often called “Duck Typing” in the dynamic world). Instead of checking what an object is, the system checks what an object can do.

The Theory: Nominal vs. Structural

This allows your code to interact with any object that meets your interface requirements, even if the objects share no common ancestor.

Glossary for Beginners

Simple Implementation: Defining an Interface

You define a Protocol to tell your system what you expect, without forcing objects to inherit from it.

from typing import Protocol

class Schedulable(Protocol):
    def run(self) -> None:
        ...

def start_job(job: Schedulable):
    job.run()

# Any class with a 'run' method will now pass type-checking automatically.

Complex Implementation: Production-Grade Service Contracts

In a production-grade microservice, you can use Protocols to define contracts between services. If a service satisfies the protocol, it can be swapped out instantly without modifying the orchestrator code.

from typing import Protocol, runtime_checkable

@runtime_checkable
class DataProvider(Protocol):
    async def get_data(self, key: str) -> dict: ...

class CacheService:
    async def get_data(self, key: str) -> dict:
        return {"data": "cached"}

# The Orchestrator doesn't care about the implementation, only the behavior
async def sync_service(provider: DataProvider):
    data = await provider.get_data("id_001")
    return data

Quick Reference: Inheritance vs. Protocol

Feature Abstract Base Class (ABC) Typing Protocol
Coupling High (Hard dependency) Low (Implicit compatibility)
Flexibility Rigid Extremely High
Design Intent Explicit Inheritance Implicit Contract
Verification Runtime (isinstance) Static (MyPy/Pyright)

Why We Choose Protocol over ABCs

We choose Protocol to achieve Dependency Inversion. It allows us to define our system’s needs in the core business logic while letting individual modules implement those needs at their own pace, without ever needing to import the core definitions. This eliminates the “dependency hell” where changing one base class forces a rebuild of 50 dependent modules.

Developer Checklist

Takeaways