intermediate
Composition over inheritance
Combine small collaborators when behavior varies independently instead of forcing a deep class hierarchy that couples unrelated changes.
Composition builds behavior by combining small collaborators—`class Report { constructor(private exporter: Exporter, private formatter: Formatter) {}`—instead of stacking overrides in a deep hierarchy. Behavior varies independently: swap `PdfExporter` without touching formatting logic.
Inheritance shares implementation but locks parent coupling; composition favors has-a relationships and explicit wiring (often via DI).
On interviews: refactor an overloaded base class into composed strategies and list what becomes easier to test.
Common pitfalls: god base classes, fragile template method overrides, and composing too many tiny objects without a clear facade.
The trade-off is more wiring boilerplate versus flexible, testable variation points.
Checklist:
- Prefer has-a when behavior mixes independently.
- Inject collaborators instead of extending.
- Keep composed objects focused (SRP).