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

FeatureInheritanceComposition
GoalReuse behavior via “is-a” relationshipReuse behavior via “has-a” relationship
CouplingTight couplingLoose coupling
FlexibilityLess flexible to changeMore flexible and modular
Code ReuseInherits all parent behaviorReuses specific behavior only when needed
Use WhenNatural hierarchy existsYou want to compose behavior dynamically
DownsidesFragile base class problem, tight couplingMay involve more boilerplate

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
class Animal:
    def speak(self):
        print("Animal sound")

class Dog(Animal):
    def speak(self):
        print("Woof!")

dog = Dog()
dog.speak()  # Output: Woof!
Composition
class Engine:
    def start(self):
        print("Engine started")

class Car:
    def __init__(self):
        self.engine = Engine()

    def start(self):
        self.engine.start()

car = Car()
car.start()  # Output: Engine started


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.
class Engine:
    def start(self):
        print("Engine starting")

class Car(Engine):  # Inheritance misuse: Car is NOT a type of Engine
    def drive(self):
        print("Car driving")

car = Car()
car.start()  # Prints "Engine starting"
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.
class Engine:
    def start(self):
        print("Engine starting")

class Car:
    def __init__(self):
        self.engine = Engine()      # Composition: Car has an Engine
    def start(self):
        print("Car starting")
        self.engine.start()
    def drive(self):
        print("Car driving")

car = Car()
car.start()
Now running car.start() prints:
Car starting
Engine starting
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.