Skip to content

Abstract Base Classes & Protocols Interview Questions & Answers

15 questions Updated 2026-06-18 Share:

Python interview questions on abstract base classes and @abstractmethod, why use ABCs, collections.abc, duck typing vs ABCs, and typing.Protocol for structural/static duck typing with runtime_checkable.

Read the in-depth guidePython Abstract Base Classes and Protocols Explained — ABCs, Duck Typing, and Protocol(opens in new tab)
15 of 15

An abstract base class (ABC) is a class that can't be instantiated directly and defines a set of methods subclasses must implement. You build one by subclassing abc.ABC and decorating required methods with @abstractmethod. Python refuses to instantiate any subclass that leaves an abstract method unimplemented.

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):            # subclasses MUST implement this
        ...

Shape()                        # TypeError — can't instantiate abstract class

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

Circle(2).area()               # 12.566... — works

@abstractmethod turns the "must implement" contract into an enforced, load-time check rather than a runtime NotImplementedError later. It defines an interface that subclasses are required to fulfill.

ABCs enforce an interface — they guarantee at instantiation time that subclasses provide the required methods, catching mistakes early instead of blowing up deep in your code. They also document intent clearly and integrate with isinstance checks, so callers can verify capabilities.

from abc import ABC, abstractmethod

class Storage(ABC):
    @abstractmethod
    def save(self, key, value): ...
    @abstractmethod
    def load(self, key): ...

class FileStorage(Storage):
    def save(self, key, value): ...
    # forgot load() ...

FileStorage()      # TypeError — flags the missing method immediately
isinstance(FileStorage, type) and issubclass(FileStorage, Storage)  # True

Use an ABC when you have a family of classes that must share a contract and you want that contract enforced. For looser, optional interfaces, plain duck typing or a Protocol may fit better.

collections.abc provides the standard ABCs for Python's container protocols — Iterable, Iterator, Sequence, Mapping, Hashable, and more. You use them two ways: as a base class to inherit mixin methods, and in isinstance checks to test whether an object supports a protocol.

from collections.abc import Iterable, Sequence, Mapping

isinstance([1, 2], Iterable)   # True
isinstance("abc", Sequence)    # True
isinstance({}, Mapping)        # True

class MyList(Sequence):        # inherit the protocol's mixin methods
    def __init__(self, data):
        self._data = data
    def __getitem__(self, i):
        return self._data[i]
    def __len__(self):
        return len(self._data)
    # get __contains__, __iter__, __reversed__, index, count for FREE

Subclassing Sequence and implementing just __getitem__ + __len__ gives you a fully functional sequence. Prefer these standard ABCs over inventing your own for container-like types.

Duck typing is "if it walks like a duck and quacks like a duck, it's a duck" — Python doesn't check the type, it just tries the operation and works if the needed methods exist. ABCs add an explicit, enforced contract on top, letting you assert membership and catch missing methods up front.

# Duck typing — no declared interface, just call and hope:
def make_it_quack(thing):
    thing.quack()          # works for ANYTHING with a quack() method

class Dog:
    def quack(self): return "woof-quack"
make_it_quack(Dog())       # works — Dog "is" a duck here

# ABC — explicit, checkable contract:
from abc import ABC, abstractmethod
class Duck(ABC):
    @abstractmethod
    def quack(self): ...

Duck typing is flexible and Pythonic but defers errors to runtime; ABCs trade some flexibility for early enforcement and clear interfaces. Use duck typing for loose coupling, ABCs when you want guarantees.

typing.Protocol enables structural typing ("static duck typing"): a class satisfies a protocol simply by having the right methods/attributes — no explicit inheritance needed. Type checkers verify the structure statically, giving you duck typing's flexibility with static safety. Add @runtime_checkable to also allow isinstance checks at runtime.

from typing import Protocol, runtime_checkable

@runtime_checkable
class Drawable(Protocol):
    def draw(self) -> str: ...     # required shape, not inheritance

class Button:                      # does NOT inherit Drawable
    def draw(self) -> str:
        return "[Button]"

def render(item: Drawable) -> str: # type checker accepts Button
    return item.draw()

render(Button())                   # works — structural match
isinstance(Button(), Drawable)     # True — thanks to @runtime_checkable

Unlike ABCs, classes don't register or subclass anything — matching the structure is enough. Note @runtime_checkable only checks method names/existence, not signatures. Use Protocols for flexible, statically verified interfaces; use ABCs when you need shared implementation or explicit registration.

Stack the decorators with @abstractmethod innermost (closest to the function). It composes with @property, @classmethod, and @staticmethod to require subclasses to provide those specific member kinds.

from abc import ABC, abstractmethod

class Config(ABC):
    @property
    @abstractmethod
    def name(self) -> str: ...        # subclasses must define a `name` property

    @classmethod
    @abstractmethod
    def from_file(cls, path): ...     # required classmethod

Rule of thumb: put @abstractmethod directly above the def, with @property/ @classmethod above it — order matters, and getting it wrong silently drops the abstractness.

MyABC.register(SomeClass) makes SomeClass a virtual subclass: isinstance/ issubclass report True without inheritance — but the ABC does not enforce that abstract methods exist and provides no mixin methods. It's a pure "I promise this conforms" assertion.

from abc import ABC, abstractmethod

class Quacker(ABC):
    @abstractmethod
    def quack(self): ...

class Duck:                      # no inheritance
    def quack(self): return "quack"

Quacker.register(Duck)
issubclass(Duck, Quacker)        # True — virtual subclass
isinstance(Duck(), Quacker)      # True
# but Python never checks Duck actually has quack()

Rule of thumb: register retrofits third-party classes into an ABC hierarchy for isinstance checks, but you lose abstractness enforcement and inherited mixins.

Overriding __subclasshook__ lets an ABC decide issubclass/isinstance by inspecting structure (e.g. "has a __len__?") rather than the inheritance tree. This is how collections.abc classes recognize unrelated types.

from abc import ABC, abstractmethod

class Sized(ABC):
    @abstractmethod
    def __len__(self): ...
    @classmethod
    def __subclasshook__(cls, C):
        if cls is Sized:
            return any("__len__" in B.__dict__ for B in C.__mro__)
        return NotImplemented

isinstance([1, 2], Sized)     # True — list has __len__, no registration

Rule of thumb: __subclasshook__ powers duck-typed isinstance (like Iterable/ Hashable); return NotImplemented to fall back to normal subclass logic.

Choose a Protocol when you only need a structural contract and don't control (or don't want to couple) the implementing classes — great for typing third-party or built-in objects. Choose an ABC when you want shared mixin code, explicit registration, or instantiation-time enforcement.

from typing import Protocol

class SupportsClose(Protocol):    # any object with close() fits, no inheritance
    def close(self) -> None: ...

def cleanup(r: SupportsClose) -> None:
    r.close()                     # files, sockets, custom — all match

Rule of thumb: Protocol = "fits by shape, no coupling, static checks"; ABC = "shared base, enforced at runtime, explicit hierarchy."

NotImplementedError is an exception you raise to signal an unfinished method. NotImplemented is a singleton value you return from binary special methods (__eq__, __add__) to tell Python "try the reflected operation." They are not interchangeable.

class Base:
    def render(self):
        raise NotImplementedError   # subclass must override

class Money:
    def __eq__(self, other):
        if not isinstance(other, Money):
            return NotImplemented    # let Python try other.__eq__
        return self.amount == other.amount

Rule of thumb: raise NotImplementedError for abstract-ish stubs; return NotImplemented from operator dunders to allow fallback/reflection.

ABCs work via the ABCMeta metaclass. abc.ABC is just a convenience base class that already uses that metaclass, so you can write class X(ABC) instead of the verbose class X(metaclass=ABCMeta). You need the metaclass form when combining with another metaclass.

from abc import ABC, ABCMeta, abstractmethod

class A(ABC):                       # the easy, common way
    @abstractmethod
    def f(self): ...

class B(metaclass=ABCMeta):         # equivalent, explicit metaclass
    @abstractmethod
    def f(self): ...

Rule of thumb: use ABC for everyday abstract classes; drop to metaclass=ABCMeta only when you must merge it with a custom metaclass.

Yes. @abstractmethod still requires the subclass to override it, but the body can hold shared logic the subclass calls via super(). This gives a default while keeping the override mandatory.

from abc import ABC, abstractmethod

class Validator(ABC):
    @abstractmethod
    def validate(self, x):
        if x is None:                 # shared precondition
            raise ValueError("None not allowed")

    # subclass:
class Positive(Validator):
    def validate(self, x):
        super().validate(x)           # reuse base checks
        return x > 0

Rule of thumb: an abstract method with a body provides reusable scaffolding that subclasses extend via super() — but the override is still required.

Yes — declare them as annotated class variables in the Protocol body. A class satisfies the protocol if it has matching attributes (commonly set in __init__), checked structurally by the type checker.

from typing import Protocol

class Named(Protocol):
    name: str            # required attribute
    def greet(self) -> str: ...

class User:
    def __init__(self, name: str):
        self.name = name          # satisfies `name: str`
    def greet(self) -> str:
        return f"hi {self.name}"

Rule of thumb: Protocols can specify both attributes and methods — annotate the attribute in the body; no value/assignment needed.

The check fires at instantiation, not definition. Defining an incomplete subclass is fine; the TypeError only happens when you try to create an instance. This lets you build deep abstract hierarchies.

from abc import ABC, abstractmethod

class A(ABC):
    @abstractmethod
    def f(self): ...

class B(A):       # OK — still abstract, no error yet
    pass

class C(B):
    def f(self): return 1

B()               # TypeError here — at instantiation
C()               # fine

Rule of thumb: abstractness is enforced when you instantiate, so intermediate abstract subclasses are legal and common.

Each container ABC lists abstract methods (you implement) and mixin methods (you get free). E.g. Mapping needs __getitem__, __len__, __iter__ and then gives you keys, items, values, get, __contains__, __eq__.

from collections.abc import Mapping

class FrozenDict(Mapping):
    def __init__(self, d): self._d = dict(d)
    def __getitem__(self, k): return self._d[k]   # required
    def __iter__(self): return iter(self._d)       # required
    def __len__(self): return len(self._d)         # required

fd = FrozenDict({"a": 1})
fd.get("a"), "a" in fd, list(fd.keys())   # all work for free

Rule of thumb: implement only the few abstract methods an abc lists and inherit the rest — far less code than building a container by hand.

More ways to practice

The self-quiz is live. Join our channel for updates, new content & tech tips.

Join our WhatsApp Channel