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()
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")
Now Car can use any engine:
car = Car(ElectricEngine())
car.start()
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)