OOP FundamentalsInheritance & CompositionHard⏱️ ~4 min

Trade-offs: When to Use Inheritance vs Composition

The Core Trade-off

The fundamental tension is between simplicity and flexibility. Inheritance provides elegant code reuse through hierarchies but creates tight coupling. Composition provides loose coupling and flexibility but requires more boilerplate and delegation.

Inheritance Advantages

First, natural polymorphism. Subclasses automatically work wherever parent is expected.

Second, less boilerplate. No need to write delegation methods.

Third, clear hierarchical relationships in domain modeling.

Inheritance Disadvantages

First, tight coupling. Changes to parent ripple to all children.

Second, fragile base class problem. Parent modifications break subclasses.

Third, inflexible. Cannot change inheritance at runtime or inherit from multiple classes (in most languages).

Composition Advantages

First, loose coupling. Components are independent and replaceable.

Second, runtime flexibility. Swap components without changing object type.

Third, better testability. Mock composed dependencies easily.

Composition Disadvantages

First, more boilerplate. Need explicit delegation methods.

Second, less intuitive for hierarchical domains.

Third, can create complex object graphs if overused.

Decision Framework
Choose Inheritance When:

First, the relationship passes the "is-a" test strictly. A SavingsAccount truly is an Account, not just behaves like one.

Second, the hierarchy is stable and unlikely to change. Core domain concepts like geometric shapes (Circle is a Shape) rarely need restructuring.

Third, you need polymorphic behavior across a clear type hierarchy. Method dispatch should work naturally without manual delegation.

Fourth, the Liskov Substitution Principle (LSP) holds. Any subclass can replace the parent without breaking correctness. If Circle cannot fully substitute Shape in all contexts, inheritance is wrong.

Choose Composition When:

First, you need to change behavior at runtime. A Logger should compose a LogDestination (file, console, network) that can be swapped, not inherit from FileLogger.

Second, the relationship is about capability, not identity. A Bird that has FlyingBehavior is better than a hierarchy where Penguin awkwardly inherits fly() and throws exceptions.

Third, you need multiple sources of behavior. A Smartphone has a Camera, GPS, and Processor. Multiple inheritance is problematic, but composition handles this naturally.

Fourth, you want to follow the Dependency Inversion Principle (DIP). High-level modules should depend on abstractions. Composing interfaces provides this naturally.

When Inheritance Becomes a Problem
Problem: Deep Hierarchies
Animal → Mammal → Carnivore → Feline → DomesticCat
Changes at Animal level ripple through five layers. Hard to understand and maintain.
Solution: Flat with Composition
Cat with DietBehavior, HabitatBehavior
Compose behaviors as needed. Each behavior is independent and testable.
When Composition is Overkill

If you are modeling a simple, stable domain like geometric shapes for a drawing application, inheritance is appropriate. A Circle is genuinely a Shape, the hierarchy will not change, and polymorphic rendering is exactly what you need. Creating a ShapeBehavior interface and composing it adds unnecessary complexity.

However, if shapes need different rendering strategies (wireframe, solid, textured), then compose a RenderStrategy instead of creating RenderableCircle, WireframeCircle subclasses.

Interview Tip: The mantra is "favor composition over inheritance," not "always use composition." Be prepared to defend inheritance when it genuinely fits. Show you understand the trade-offs, not just the rule.
Red Flags for Inheritance

First, you find yourself writing empty methods or throwing NotSupportedException. This violates LSP. A Penguin inheriting fly() from Bird and throwing an error means inheritance was the wrong choice.

Second, you want to change parent behavior for one child but not others. This indicates the parent is too broad or children are not true specializations.

Third, you have a combinatorial explosion of subclasses. If you need HybridCar, ElectricCar, GasCar, LuxuryHybridCar, LuxuryElectricCar, you should compose FuelType and LuxuryLevel instead.

💡 Key Takeaways
Favor composition over inheritance, but understand when inheritance is appropriate
Inheritance is right for stable, hierarchical domains that pass the is-a test
Composition provides flexibility for runtime behavior changes and multiple capabilities
Deep inheritance hierarchies and LSP violations signal wrong use of inheritance
Combinatorial subclass explosion indicates need for composition
📌 Examples
1Vehicle hierarchy: appropriate use of inheritance
2Payment strategy: appropriate use of composition
3Bird-Penguin with fly(): inheritance violation
4Smartphone with Camera, GPS: composition for multiple capabilities
← Back to Inheritance & Composition Overview