OBJECT-ORIENTED FOUNDATIONS / OBJECT DESIGN BRIEF

Composition over inheritance

Composition over inheritance means building behavior by combining objects (has-a) rather than extending classes (is-a).

IntermediatePhase 01 / Topic 6 of 7ResponsibilitiesCollaborationsExtensibility
01

Overview

Composition over inheritance means building behavior by combining objects (has-a) rather than extending classes (is-a). Instead of Duck extends FlyingAnimal, a Duck has a FlyBehavior. Behaviors can be mixed, swapped at runtime, and tested independently.

Inheritance creates a fixed, compile-time relationship and exposes parent internals; composition depends only on the component's public interface. Many design patterns (Strategy, Decorator, State, Bridge) are composition techniques. The rule is not 'never inherit' but 'inherit for true subtypes, compose for reuse'.

LEGO vs a molded toy

A molded toy car is one fixed shape. With LEGO, you snap on different wheels, engines, and bodies, and you can swap parts later without making a new toy.

02

When to use it

  • Combinations of behaviors would create a class explosion (FlyingSwimmingQuackingDuck).
  • Behavior should change at runtime.
  • You want to reuse code without inheriting an entire parent API.
03

Where it shows up in interviews

Class explosion

Recognize it when: subclasses for every combination of features.

  • Design a coffee machine with add-ons
  • Design game characters with abilities
Runtime behavior change

Recognize it when: behavior depends on configuration or state.

  • Design a vending machine
  • Design pricing rules
04

Where it is used in real software

React

The React team recommends composition (props, children, hooks) and has never recommended component inheritance hierarchies.

Game engines

Unity and ECS architectures attach components (Collider, Renderer, Health) to entities instead of deep class trees.

Java I/O

BufferedReader wraps a Reader: behavior is composed, not inherited per combination.

05

Key terms

Composition
An object holds references to other objects that do work for it.
Delegation
Forwarding a call to a composed object.
Has-a vs is-a
Composition vs inheritance relationship.
Class explosion
Combinatorial growth of subclasses.
06

How it works, step by step

  1. 1
    Identify the varying behaviors

    Flying, swimming, quacking.

  2. 2
    Define an interface per behavior

    FlyBehavior, SoundBehavior.

  3. 3
    Implement behaviors separately

    FlyWithWings, NoFly, Squeak.

  4. 4
    Inject behaviors into the object

    Constructor or setter.

  5. 5
    Delegate calls

    duck.fly() calls this.flyBehavior.fly().

07

Class explosion vs composition

Coffee with optional milk, sugar, and caramel

Step 1 / 2
ApproachClasses needed for 3 add-onsAdd a 4th add-on
Inheritance per combination2^3 = 8 subclasses16 subclasses
Composition (list of add-ons or decorators)1 Coffee + 3 add-on classes+1 class

NOWApproach: Inheritance per combination | Classes needed for 3 add-ons: 2^3 = 8 subclasses | Add a 4th add-on: 16 subclasses

Composition grows linearly with features; inheritance per combination grows exponentially.

08

Implementation

interface MoveBehavior { move(): string }interface AttackBehavior { attack(): string } const walk: MoveBehavior = { move: () => "walks" };const fly: MoveBehavior = { move: () => "flies" };const sword: AttackBehavior = { attack: () => "slashes with a sword" };const fireball: AttackBehavior = { attack: () => "casts a fireball" }; class Character {  constructor(readonly name: string, private mover: MoveBehavior, private attacker: AttackBehavior) {}  act() { return `${this.name} ${this.mover.move()} and ${this.attacker.attack()}`; }  equip(attacker: AttackBehavior) { this.attacker = attacker; } // change at runtime} const knight = new Character("Knight", walk, sword);const dragon = new Character("Dragon", fly, fireball);knight.equip(fireball); // no new subclass needed
09

Complexity and performance

Classes for n independent featuresO(n)

vs O(2^n) with inheritance.

Runtime cost1 delegation call

Negligible.

10

Trade-offs

Flexibility vs boilerplate

Composition needs forwarding methods and wiring; inheritance gives reuse for free.

Discoverability

Behavior spread across collaborators can be harder to trace than one class.

11

Variants and related techniques

Strategy and Decorator

Classic patterns built on composition.

Mixins and traits

Language features for horizontal reuse (Scala traits, Kotlin delegation, TypeScript mixins).

12

Common mistakes

  • Dogmatically avoiding all inheritance.

    Fix: True subtype hierarchies (exceptions, sealed shapes) are fine.

  • Exposing composed objects directly.

    Fix: Delegate through meaningful methods to keep encapsulation.

13

Interview questions

Why prefer composition over inheritance?

Composition depends only on public interfaces, allows runtime changes, avoids class explosion and fragile base classes, and makes components independently testable.

Give an example where inheritance is still appropriate.

A true is-a hierarchy with a stable contract, such as exception types or a sealed set of shapes where every subtype honors the parent's behavior.

14

Practice problems

ProblemDifficultyWhat it trains
Refactor a Duck hierarchy with behaviorsEasyStrategy composition.
Design a pizza builder with toppingsMediumAvoid class explosion.