When should you use inheritance vs composition?
Inheritance and composition are both mechanisms for code reuse and building relationships between types, but they couple classes differently and suit different design problems.
**Inheritance (is-a relationship)** models a subtype relationship. `Dog extends Animal` means a Dog IS-A Animal and gets all of Animal's behavior. The subclass can override or extend parent behavior. The coupling is deep: the subclass is bound to the parent's implementation details, not just its interface. Changes to the parent class propagate to all subclasses, often unexpectedly.
**Composition (has-a relationship)** models ownership. A `Car` HAS-A `Engine`. The Car class holds a reference to an Engine and delegates behavior to it. The coupling is loose — Car depends on Engine's interface, not its implementation. Swap a `GasEngine` for an `ElectricEngine` and Car doesn't change.
**Why prefer composition?**
1. **Flexibility at runtime**: composed objects can be swapped at construction time or even at runtime. Inheritance hierarchies are fixed at compile time. 2. **Testability**: you can inject a mock Engine into Car. You can't easily mock a parent class without framework support. 3. **Avoiding the fragile base class problem**: changing a base class method signature or behavior can silently break all subclasses — a well-known danger in deep hierarchies. 4. **Avoiding the diamond problem**: multiple inheritance of implementation causes ambiguity (Python has MRO to resolve it; Java disallows it for classes). Composition sidesteps this. 5. **Single Responsibility**: behavior is encapsulated in focused components rather than bloated base classes.
**When to use inheritance:** - The relationship is genuinely polymorphic: you need to treat `Dog` and `Cat` as `Animal` via a common interface - The subclass truly IS-A supertype and the Liskov Substitution Principle holds (a `Dog` can substitute for an `Animal` everywhere) - Framework contracts require it (Django's `View` class, Java's `Thread`) - The hierarchy is shallow (1-2 levels) and stable
**The rule of thumb**: if you are inheriting to reuse code rather than to model a true subtype relationship, prefer composition. Abstract classes or interfaces define contracts; composition provides implementation reuse without coupling. The Gang of Four's advice — "favor object composition over class inheritance" — remains as relevant as ever.
| Aspect | Inheritance | Composition |
|---|---|---|
| Relationship | Is-a (subtype) | Has-a (ownership/delegation) |
| Coupling | Tight (impl details exposed) | Loose (interface only) |
| Flexibility | Fixed at compile time | Swappable at construction/runtime |
| Testability | Hard (mocking base class) | Easy (inject mock dependencies) |
| Behavior reuse | Via override/extension | Via delegation |
| Fragility | Fragile base class problem | Isolated — changes don't cascade |
| Use when | True LSP-compliant subtype | Code reuse, flexibility, testability |
Correctly defines is-a vs. has-a relationship, explains the fragile base class problem, and gives a concrete example of refactoring inheritance to composition.
Covers Liskov Substitution Principle as the criterion for valid inheritance, explains runtime flexibility of composition, addresses testability with dependency injection, and cites Gang of Four guidance.
Reading the answer is step one. Explaining it unprompted — under interview pressure — is what actually matters. Get AI-graded feedback on your answer with follow-up probes on your weak points.
Get Graded — Free Assessment