Bird
Raised Fist0
LLDsystem_design~7 mins

Composition over inheritance in LLD - System Design Guide

Choose your learning style10 modes available
Problem Statement
When using inheritance excessively, code becomes rigid and tightly coupled. Changes in a parent class can unexpectedly break child classes, and deep inheritance trees make understanding and modifying behavior difficult.
Solution
Composition solves this by building classes from smaller, reusable components that provide specific behaviors. Instead of inheriting from a base class, objects contain instances of these components, allowing flexible combinations and easier changes without affecting unrelated parts.
Architecture
Component
Behavior A
↓
Composite
Composite

This diagram shows a composite object containing two separate behavior components, illustrating how composition assembles functionality by combining smaller parts.

Trade-offs
✓ Pros
→
Improves flexibility by allowing dynamic behavior changes at runtime.
→
Reduces tight coupling between classes, making code easier to maintain.
→
Avoids problems of deep inheritance hierarchies and fragile base classes.
→
Encourages reuse of small, focused components across different classes.
✗ Cons
→
Requires more upfront design to identify reusable components.
→
Can lead to more objects and indirection, which might slightly impact performance.
→
May increase complexity if components are not well organized or documented.
Use when your system needs flexible behavior combinations or when inheritance hierarchies become complex and hard to maintain, typically in medium to large codebases.
Avoid when the system is very simple with minimal behavior variations, or when performance is critical and object indirection overhead is unacceptable.
Real World Examples
Spotify
Spotify uses composition in their client apps to combine different playback and UI behaviors dynamically without deep inheritance trees.
Netflix
Netflix applies composition in their microservices to assemble different capabilities like caching, logging, and retry logic as separate components.
Airbnb
Airbnb uses composition in their frontend React components to mix and match UI behaviors and styles flexibly.
Code Example
The before code shows inheritance where Dog and Cat extend Animal and override speak, causing tight coupling. The after code uses composition by injecting behavior objects into Animal, allowing flexible behavior changes without inheritance.
LLD
### Before: Inheritance (rigid and tightly coupled)
class Animal:
    def speak(self):
        return "Some sound"

class Dog(Animal):
    def speak(self):
        return "Bark"

class Cat(Animal):
    def speak(self):
        return "Meow"

### After: Composition (flexible and reusable)
class SpeakBehavior:
    def speak(self):
        pass

class BarkBehavior(SpeakBehavior):
    def speak(self):
        return "Bark"

class MeowBehavior(SpeakBehavior):
    def speak(self):
        return "Meow"

class Animal:
    def __init__(self, speak_behavior):
        self.speak_behavior = speak_behavior
    def speak(self):
        return self.speak_behavior.speak()

# Usage
dog = Animal(BarkBehavior())
cat = Animal(MeowBehavior())
print(dog.speak())  # Bark
print(cat.speak())  # Meow
OutputSuccess
Alternatives
Inheritance
Inheritance creates a rigid parent-child relationship where child classes extend parent behavior directly.
Use when: Choose inheritance when behavior is stable, hierarchical, and unlikely to change frequently.
Mixin classes
Mixins provide reusable behavior by multiple inheritance, but can cause complexity and conflicts.
Use when: Use mixins when you need to share behavior across unrelated classes but want to avoid deep inheritance.
Summary
Excessive inheritance creates rigid and fragile code structures that are hard to change.
Composition builds objects from reusable behavior components, improving flexibility and maintainability.
Many large companies use composition to manage complex behavior without deep inheritance trees.

Practice

(1/5)
1. What is the main advantage of using composition over inheritance in system design?
easy
A. It allows combining simple parts to build complex behavior flexibly.
B. It forces a strict class hierarchy that is easier to follow.
C. It eliminates the need for interfaces or contracts.
D. It automatically improves system performance.

Solution

  1. Step 1: Understand composition concept

    Composition means building complex systems by combining smaller, reusable parts.
  2. Step 2: Compare with inheritance

    Inheritance creates rigid class hierarchies, while composition offers flexibility and modularity.
  3. Final Answer:

    It allows combining simple parts to build complex behavior flexibly. -> Option A
  4. Quick Check:

    Composition = Flexible combination [OK]
Hint: Composition means combining parts, inheritance means extending classes [OK]
Common Mistakes:
  • Thinking inheritance is always more flexible
  • Confusing composition with interface implementation
  • Believing composition removes need for design contracts
2. Which of the following code snippets correctly demonstrates composition in a class design?
easy
A. class Car extends Engine { }
B. class Engine { } class Car { Engine engine; }
C. class Car implements Engine { }
D. class Engine extends Car { }

Solution

  1. Step 1: Identify composition syntax

    Composition means a class contains an instance of another class as a member.
  2. Step 2: Analyze options

    class Engine { } class Car { Engine engine; } shows Car has an Engine instance, which is composition. Others use inheritance or interface incorrectly.
  3. Final Answer:

    class Engine { } class Car { Engine engine; } -> Option B
  4. Quick Check:

    Composition = class has member object [OK]
Hint: Composition means 'has-a' relationship, not 'is-a' [OK]
Common Mistakes:
  • Confusing inheritance (extends) with composition
  • Using implements keyword incorrectly for composition
  • Assuming inheritance is composition
3. Given the following code, what will be the output?
class Battery {
  int capacity = 100;
}
class Phone {
  Battery battery = new Battery();
  int getBatteryCapacity() {
    return battery.capacity;
  }
}
Phone p = new Phone();
System.out.println(p.getBatteryCapacity());
medium
A. Compilation error
B. 0
C. NullPointerException
D. 100

Solution

  1. Step 1: Understand composition usage

    Phone class contains a Battery instance initialized with capacity 100.
  2. Step 2: Trace method call

    Calling getBatteryCapacity returns battery.capacity which is 100.
  3. Final Answer:

    100 -> Option D
  4. Quick Check:

    Composition returns member value correctly [OK]
Hint: Composition accesses member objects directly [OK]
Common Mistakes:
  • Assuming battery is null without initialization
  • Confusing inheritance with composition access
  • Expecting compilation error due to missing constructor
4. Identify the error in the following code that tries to use composition:
class Screen { }
class Laptop {
  Screen screen;
  Laptop() {
    screen = null;
  }
  void display() {
    System.out.println(screen.toString());
  }
}
medium
A. screen is not initialized before use, causing NullPointerException
B. Screen class should extend Laptop for composition
C. display method should return a value, not print
D. Laptop constructor should not assign null to screen

Solution

  1. Step 1: Check initialization of composed object

    screen is assigned null in constructor, so it is not a valid object.
  2. Step 2: Analyze method usage

    Calling screen.toString() on null causes NullPointerException at runtime.
  3. Final Answer:

    screen is not initialized before use, causing NullPointerException -> Option A
  4. Quick Check:

    Null object in composition causes runtime error [OK]
Hint: Always initialize composed objects before use [OK]
Common Mistakes:
  • Thinking inheritance is needed for composition
  • Ignoring null initialization issues
  • Confusing method return type with composition error
5. You are designing a payment system where different payment methods (credit card, PayPal, crypto) share common steps but have unique processing. How does using composition over inheritance improve your design?
hard
A. Composition requires duplicating code for each payment method, increasing maintenance.
B. Inheritance forces all payment methods to share the same code, reducing flexibility.
C. By composing payment method objects, you can add or change methods without altering a deep class hierarchy.
D. Inheritance allows runtime swapping of payment methods more easily than composition.

Solution

  1. Step 1: Understand payment system requirements

    Different payment methods share steps but differ in processing details.
  2. Step 2: Analyze composition benefits

    Composition lets you build payment methods as separate objects combined in the system, enabling easy addition or change without deep inheritance.
  3. Step 3: Contrast with inheritance

    Inheritance creates rigid hierarchies that are hard to modify and extend for new payment types.
  4. Final Answer:

    By composing payment method objects, you can add or change methods without altering a deep class hierarchy. -> Option C
  5. Quick Check:

    Composition = flexible, modular payment design [OK]
Hint: Use composition to add features without changing base classes [OK]
Common Mistakes:
  • Assuming inheritance is always simpler for new features
  • Believing composition duplicates code unnecessarily
  • Confusing runtime flexibility between inheritance and composition
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard