datarekha
Python Medium Asked at GoogleAsked at MetaAsked at StripeAsked at AirbnbAsked at Microsoft

When should you use composition instead of inheritance in Python, and what are the design signals for each?

The short answer

Inheritance models an 'is-a' relationship and is appropriate when a subclass is genuinely a specialisation of the parent. Composition models a 'has-a' relationship and is preferred when you want to reuse behaviour without coupling to a class hierarchy — it is more flexible, easier to test, and avoids the fragile base-class problem.

How to think about it

The classic framing is is-a vs has-a. A Dog is an Animal — inheritance fits. A Car has an Engine — composition fits. The harder part is recognising when inheritance is being abused, which is much more common in interview code review scenarios.

What’s really being tested

The interviewer wants to see that you understand the Liskov Substitution Principle (can I swap a subclass anywhere its parent is expected, without surprises?) and the fragile base-class problem (what happens when the parent changes?). Composition sidesteps both issues.

Step 1 — The fragile base-class problem

When you override a method in a subclass, you’re betting that the parent’s other methods will never call your override in a surprising way. That bet gets riskier as the class grows.

Step 2 — Composition decouples the pieces

Instead of inheriting behaviour, inject it. The class has a collaborator rather than being a specialisation. Swapping the collaborator requires zero changes to the outer class.

Step 3 — Dependency injection makes testing trivial

Because the collaborator is passed in, tests can inject a stub or mock without subclassing anything.

When inheritance IS the right choice

  • The subclass is a genuine subtype — the Liskov Substitution Principle holds: anywhere a Base is expected, a Sub works without surprises.
  • You need polymorphic dispatch enforced by an ABC.
  • The hierarchy is shallow (one or two levels). Deep hierarchies almost always signal a composition opportunity.

Mixing both — ABCs + composition

from abc import ABC, abstractmethod

class Formatter(ABC):
    @abstractmethod
    def format(self, record: dict) -> str: ...

class JSONFormatter(Formatter):
    def format(self, record: dict) -> str:
        import json
        return json.dumps(record)

class Handler(ABC):
    def __init__(self, formatter: Formatter) -> None:
        self._fmt = formatter   # composed

    @abstractmethod
    def emit(self, record: dict) -> None: ...

Inheritance defines the what (the interface); composition wires the how (the implementation) together at runtime.

Learn it properly Inheritance vs Composition

Keep practising

All Python questions

Explore further

Skip to content