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
Interview Prep
Practice concise answers, then expand each card for the explanation.
1BeginnerQuestionWhat does 'favor composition over inheritance' mean?+
Answer
2BeginnerQuestionGive an example of composition.+
Answer
3IntermediateQuestionWhen is inheritance still correct?+
Answer
4IntermediateQuestionHow does composition support testing?+
Answer
5AdvancedQuestionRefactor inherited Stack with push logging to composition.+
Answer
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.