DEV Community

Saras Growth Space
Saras Growth Space

Posted on

LLD Object-Oriented Design: Composition vs Inheritance (Choosing the Right Relationship)

After learning inheritance and polymorphism, the next critical design decision is:

How should objects be connected to each other?

There are two primary ways:

  • Inheritance (is-a relationship)
  • Composition (has-a relationship)

Understanding when to use which is one of the most important skills in LLD.


Inheritance Recap (is-a)

Inheritance means:

A child class is a specialized version of a parent class.

Example:

  • Dog is an Animal
  • Car is a Vehicle

It creates a hierarchical relationship.


Composition (has-a Relationship)

Composition means:

An object is made up of other objects.

Instead of inheriting behavior, you delegate it to another object.


Example

class Engine:
    def start(self):
        print("Engine started")

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

    def start(self):
        self.engine.start()
Enter fullscreen mode Exit fullscreen mode

Here:

  • Car HAS an Engine
  • behavior is delegated, not inherited

Key Difference

Inheritance Composition
is-a relationship has-a relationship
tight coupling loose coupling
rigid hierarchy flexible design
behavior inherited behavior delegated

Why Composition is Preferred in LLD

In real systems:

  • requirements change frequently
  • behavior varies across contexts
  • rigid hierarchies break easily

Composition provides:

  • flexibility
  • reusability
  • easier maintenance

Problem with Overusing Inheritance

Inheritance can lead to:

  • deep class hierarchies
  • fragile base classes
  • unexpected side effects
  • tightly coupled systems

As systems grow:

inheritance becomes difficult to manage


Composition Enables Flexibility

With composition, behavior can be changed dynamically.

Example:

class Engine:
    def start(self):
        print("Generic engine start")

class ElectricEngine(Engine):
    def start(self):
        print("Electric engine start")
Enter fullscreen mode Exit fullscreen mode

Now Car can use any engine:

car = Car(ElectricEngine())
car.start()
Enter fullscreen mode Exit fullscreen mode

Core Design Insight

Composition allows behavior to be plugged in, while inheritance locks behavior into a hierarchy.


Real-World Analogy

Think of a car:

  • Car does NOT become an engine
  • Car simply uses an engine

Similarly:

  • objects should use components, not become them

When to Use Inheritance

Use inheritance when:

  • clear is-a relationship exists
  • behavior is stable
  • substitution is valid

Example:

  • Payment methods
  • Base models
  • Shared contracts

When to Use Composition

Use composition when:

  • behavior can change
  • multiple variations exist
  • flexibility is required

Example:

  • notification systems
  • payment processing pipelines
  • service integrations

Common Mistake

Beginners often:

  • force inheritance for reuse
  • create unnecessary hierarchies
  • mix unrelated behaviors into base classes

This leads to:

rigid and fragile systems


Design Principle

Prefer composition over inheritance for flexible and maintainable systems.


One-Line Takeaway

Inheritance defines hierarchy, but composition defines flexibility through collaboration.

Top comments (0)