Skip to main content
Composition is a design principle where one class contains objects of other classes to reuse functionality instead of inheriting it. Inheritance models an “is-a” relationship: a subclass extends a base class and reuses its interface and implementation . Composition models a “has-a” relationship: a class contains one or more objects of other classes as members . For example, a Car is a Vehicle (inheritance), but a Car has an Engine (composition) . Composition tends to yield more modular and flexible designs than deep inheritance hierarchies . Inheritance (is-a): Only use it when there’s a genuine subtype relationship that obeys Liskov’s substitution principle. For instance, a Dog class can inherit from Animal . Inheritance can lead to tight coupling – changes in a base class may break subclasses . A classic pitfall is giving a subclass behaviors it can’t use: e.g. a Penguin inheriting Bird.fly() behaves incorrectly . Composition (has-a): A class contains other classes as components. For example, a Car contains an Engine instance. Composition decouples classes and allows swapping or reusing parts. You can easily replace one component (say a gas engine with an electric engine) without altering the container class. Guideline: Favor composition over inheritance unless there’s a clear is-a relationship. Composition leads to more maintainable, testable code. Inheritance should only be used when subclasses truly extend base behavior without breaking it

Inheritance vs Composition

When to use Composition?

  • You want to combine behaviors flexibly.
  • You’re designing for loose coupling and testability.
  • You want to avoid unintended inheritance side effects.

Inheritance vs Composition Minimal example

Inheritance
Composition

Example: Car and Engine

Inheritance (bad design): Using inheritance wrongly can produce inflexible code. In this example, Car inherits from Engine – implying “Car is an Engine,” which is semantically incorrect. This violates design clarity (a subclass shouldn’t introduce impossible behavior) and tightly couples Car to Engine.
Although the code runs, it’s conceptually wrong: a Car “is not an” Engine. Inheritance here forces Car to inherit all of Engine’s behavior, which is inflexible and misleading. Composition (better design): Instead, give Car an Engine object. Now Car has an engine, which it uses internally. The classes are decoupled: we can modify or replace Engine without altering Car.
Now running car.start() prints:
The Car class delegates to its Engine object, embodying a has-a relationship. This design is more realistic and flexible: we could subclass or swap in a different engine implementation without changing Car. Modern design advice is to “favor composition over inheritance” for such cases.