Composition vs Inheritance
Build behavior by combining objects (has-a) rather than extending classes (is-a). It is looser, more flexible, and easier to change.
Level: intermediate
What is it?
Composition builds a class from other objects it holds and delegates to; it expresses 'has-a'. Inheritance builds a class by extending another; it expresses 'is-a'. The design advice 'favor composition over inheritance' comes from experience: composed objects are swapped at run time and coupled only through their interfaces, while subclasses are locked to their parents.
Why does it matter?
Hierarchies harden early and are hard to change; every new variation (electric and gas engines, flying and swimming robots) multiplies subclasses. Composition lets you combine independent parts instead, and change one part without touching the rest.
Mental model
Lego versus a sculpture carved from one block. Inheritance is carving: once you cut a shape, you are stuck with it. Composition is bricks: swap the wheels, keep the chassis, add a different engine.
How it works
- Inherit to model a true is-a relationship with a stable contract; compose to reuse behavior.
- Delegate: the outer object forwards calls to the inner one it holds.
- Inject the collaborator through the constructor so it can be replaced, including with a test fake.
- Composition avoids the diamond problem and fragile base classes because there is no shared internals to depend on.
- The Strategy, Decorator, and Adapter patterns are all composition in different arrangements.
Steps
- Identify the varying behavior
- Extract it behind an interface
- Hold it as a field
- Delegate calls to it
- Swap the part to change behavior
Code example: A Car has an engine and does not extend one
class Engine:
def start(self):
return "vroom"
class ElectricMotor:
def start(self):
return "hum"
class Car:
def __init__(self, engine): # has-a, injected
self.engine = engine
def start(self):
return self.engine.start() # delegate
print(Car(Engine()).start(), Car(ElectricMotor()).start()) # vroom humReal-world usage
- Dependency injection in services and frameworks
- Entity-component systems in games
- Wrappers and decorators around streams, handlers, and middleware
Common mistakes
- Subclassing a collection just to reuse a couple of methods.
- Deep hierarchies where each level adds one small variation.
- Composing with concrete classes so nothing can be swapped anyway.
Interview questions
Why is 'favor composition over inheritance' good advice?
Inheritance couples a subclass to its parent's implementation and fixes the relationship at compile time. Composition couples objects only through interfaces and lets you change behavior at run time, with fewer ripple effects.
When is inheritance the right choice?
When there is a genuine is-a relationship, the superclass was designed for extension, and substitutability holds — for example, exception hierarchies or framework base classes.
What is delegation?
An object handles a request by forwarding it to another object it holds, instead of implementing the behavior itself or inheriting it.