Composition vs Inheritance
Composition builds objects by combining other objects (has-a).
Introduction
Composition builds objects by combining other objects (has-a). Inheritance specializes types (is-a). The Gang of Four advice — favor composition over inheritance — appears in nearly every LLD interview because composition reduces coupling and fragile base classes.
Example: an Elevator has a Motor and Door, it is not a Motor. A Car is a Vehicle — inheritance fits. Choosing wrong damages extensibility.
This lesson gives decision rules and refactoring paths when you inherit too eagerly.
Understanding the topic
Key concepts
- Composition: delegate behavior to composed objects.
- Inheritance: subtype polymorphism with shared interface.
- Composition avoids exposing superclass internals.
- Inheritance ties you to parent implementation changes.
- Delegation pattern: outer object forwards calls to inner.
- Mix: implement interface + compose helper (e.g., List decorator).
flowchart TBEncapsulation --> AbstractionAbstraction --> InheritanceInheritance --> Polymorphism
Step-by-step explanation
- Ask: is it truly a subtype? If no, compose.
- Identify behaviors to reuse — extract to separate class, inject.
- Expose composed object only through outer class methods.
- Use interface on outer class matching client needs.
- Replace inheritance with composition when subclass overrides most methods.
- Document owned lifecycle — outer creates/destroys components.
Informative example
Car composes Engine — does not extend Engine:
public final class Engine {public void start() { /* ... */ }public void stop() { /* ... */ }}public final class Car {private final Engine engine;private final String vin;public Car(String vin, Engine engine) {this.vin = vin;this.engine = engine;}public void startTrip() {engine.start();}public void endTrip() {engine.stop();}}
If ElectricEngine and GasEngine differ, inject Engine interface implementations — Car stays stable.
Real-world use
Real-world applications
- Replacing instanceof stacks with composed strategies.
- Building rich domain objects from value components.
- Testability — mock composed collaborators.
Best practices
- Default to composition for reuse of behavior.
- Use inheritance when LSP-safe substitution is required.
- Keep composed objects private.
- Inject interfaces for composed parts.
- Avoid multi-level inheritance; prefer flat composition trees.
Common mistakes
- Subclassing HashMap to add logging (fragile — use wrapper).
- Inheriting utility class for random helper methods.
- Exposing composed objects for direct mutation.
- Deep inheritance where strategy object would suffice.
Advanced interview questions
Q1BeginnerWhat does 'favor composition over inheritance' mean?
Q2BeginnerGive an example of composition.
Q3IntermediateWhen is inheritance still correct?
Q4IntermediateHow does composition support testing?
Q5AdvancedRefactor inherited Stack with push logging to composition.
Summary
Composition = has-a; inheritance = is-a. Default to composition for behavior reuse. Inheritance when subtyping and LSP apply. Delegation keeps outer API stable. Composition simplifies testing and extension.