What's the difference between composition and inheritance, and when do you prefer one over the other?

6 minintermediateoopcompositiondesign

Quick Answer

**Inheritance** models an "is-a" relationship and reuses behavior by subclassing; **composition** models a "has-a" relationship and reuses behavior by holding a reference to another object and delegating to it. Composition is generally preferred because it avoids tight coupling to a base class's implementation and stays flexible when requirements change — the common guideline is "favor composition over inheritance" and reserve inheritance for genuine is-a relationships with stable, well-designed base classes.

Detailed Answer

Inheritance: is-a

class Vehicle:
    def start_engine(self):
        return "engine started"

class Car(Vehicle):   # a Car IS-A Vehicle
    def honk(self):
        return "beep!"

Car automatically gets everything Vehicle provides. This works well when the relationship is genuinely "is-a" and stable — a Car really is a kind of Vehicle, and that's unlikely to change.

Composition: has-a

class Engine:
    def start(self):
        return "engine started"

class Car:            # a Car HAS-A Engine (delegates to it)
    def __init__(self):
        self.engine = Engine()

    def start_engine(self):
        return self.engine.start()

Car doesn't inherit Engine's behavior — it holds an Engine instance and delegates to it. This decouples Car from Engine's implementation: swapping in an ElectricEngine just means constructing a different object, with no class-hierarchy changes.

Why composition is usually favored

  • Looser coupling: a subclass inherits everything from its parent, including internals you may not want or that may change unexpectedly; composition only exposes what you explicitly delegate to.
  • Flexibility at runtime: you can swap a composed object (car.engine = ElectricEngine()) without touching the class definition; swapping inherited behavior requires changing the class hierarchy.
  • Avoids fragile base class problems: changes to a deeply inherited base class can silently break subclasses several levels down; composed objects have an explicit, narrower contract.
  • Sidesteps multiple-inheritance/MRO complexity: composing several collaborator objects avoids diamond-inheritance and MRO conflicts entirely.

When inheritance is still the right call

  • The relationship is genuinely "is-a" and unlikely to need swapping (e.g., class JSONDecodeError(ValueError) — a JSON decode error really is a kind of value error, permanently).
  • You want to reuse a stable, well-tested base class's behavior via polymorphism (framework base classes like Django's Model, or ABCs meant specifically to be subclassed).
  • Mixins for adding small, orthogonal, well-isolated behavior (LoggingMixin, TimestampMixin) — this is inheritance used in a composition-like, capability-additive way.

Interview-ready summary: Inheritance reuses behavior through an is-a relationship and a class hierarchy; composition reuses behavior through a has-a relationship and object delegation. Composition is usually the safer default because it's more loosely coupled and flexible — reserve inheritance for genuinely stable is-a relationships or narrow, well-scoped mixins.