What's the difference between an abstract base class (ABC) and a `Protocol`?

6 minadvancedoopabcprotocoltyping

Quick Answer

`ABC` (via `abc.ABC`/`@abstractmethod`) enforces **nominal** typing — a class must explicitly inherit from the ABC to be recognized as implementing it, and Python raises `TypeError` at instantiation if abstract methods are left unimplemented. `Protocol` (PEP 544) enforces **structural** typing — a class satisfies the protocol just by having matching methods, with no inheritance required, checked statically by `mypy` (and optionally at runtime with `@runtime_checkable`).

Detailed Answer

ABC: nominal typing, enforced at instantiation

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self) -> float: ...

class Circle(Shape):
    def __init__(self, r):
        self.r = r
    def area(self):
        return 3.14159 * self.r ** 2

Shape()          # TypeError: Can't instantiate abstract class Shape
Circle(5).area()  # 78.5...

class Incomplete(Shape):
    pass

Incomplete()      # TypeError: Can't instantiate abstract class Incomplete
                   # with abstract method area

Shape explicitly declares a contract; Circle must explicitly inherit from Shape to be treated as one, and Python enforces at runtime that every abstract method is overridden before a concrete subclass can even be instantiated.

Protocol: structural typing, checked statically

from typing import Protocol, runtime_checkable

@runtime_checkable
class HasArea(Protocol):
    def area(self) -> float: ...

class Circle:                 # note: NOT inheriting from HasArea at all
    def __init__(self, r):
        self.r = r
    def area(self):
        return 3.14159 * self.r ** 2

def total_area(shapes: list[HasArea]) -> float:
    return sum(s.area() for s in shapes)

total_area([Circle(5)])            # type-checks fine — Circle matches structurally
isinstance(Circle(5), HasArea)     # True, because @runtime_checkable was used

Circle never mentions HasAreamypy (or isinstance, with @runtime_checkable) verifies the match purely by comparing method signatures.

Key differences

ABCProtocol
Typing styleNominal (explicit inheritance)Structural (duck typing, formalized)
EnforcementRuntime, at instantiationStatic, by type checker (isinstance only with @runtime_checkable, and only checks method presence, not signatures)
CouplingImplementer must know about/import the ABCImplementer needs no knowledge of the Protocol
Best forA shared base with common implementation/contract you ownDescribing a shape third-party or unrelated classes already have

When to use which

Use ABC when you own the class hierarchy and want a hard runtime guarantee that subclasses implement required methods (and possibly want to share concrete helper methods too). Use Protocol when you're describing an interface that unrelated or third-party classes already satisfy, and you only need static verification, not a runtime contract.

Interview-ready summary: ABC is nominal typing with runtime enforcement — you must inherit from it, and instantiation fails if abstract methods are missing. Protocol is structural typing checked statically — any class with matching methods satisfies it, no inheritance required, which is ideal for describing capabilities of classes you don't control.