Skip to content
intermediatePhase 49 · Low-Level Design

Inheritance

Model IS-A relationships while preferring composition.

30m
0 problems
Topic Progress0%

IS-A Relationship

Inheritance models the IS-A relationship where a subclass is a specialized version of its superclass.

The IS-A Test

Ask: "Is [subclass] a [superclass]?"

  • Dog IS-A Animal ✓
  • SavingsAccount IS-A BankAccount ✓
  • Circle IS-A Shape ✓
  • Car IS-A Engine ✗ (Car HAS-A Engine)

Inheritance Hierarchy

              ┌─────────┐
              │ Animal  │
              └────┬────┘
          ┌────────┴────────┐
    ┌─────┴─────┐    ┌─────┴─────┐
    │   Dog     │    │   Cat     │
    └─────┬─────┘    └───────────┘
          │
    ┌─────┴─────┐
    │  Puppy    │
    └───────────┘
public class Animal {
    protected String name;
    protected int age;
    
    public void eat() {
        System.out.println(name + " is eating");
    }
    
    public void sleep() {
        System.out.println(name + " is sleeping");
    }
}

public class Dog extends Animal {
    private String breed;
    
    public void bark() {
        System.out.println(name + " is barking");
    }
}

public class Puppy extends Dog {
    public void play() {
        System.out.println(name + " is playing");
    }
}

Liskov Substitution Principle (LSP)

Subclasses must be substitutable for their base class without altering correctness:

// Good: LSP satisfied
public class Bird {
    public void fly() { ... }
}

public class Sparrow extends Bird {
    // Sparrow can fly - LSP satisfied
}

// Bad: LSP violated
public class Ostrich extends Bird {
    @Override
    public void fly() {
        throw new UnsupportedOperationException(); // LSP violation!
    }
}

Common Inheritance Hierarchies

Domain Models:

PaymentMethod
├── CreditCard
├── DebitCard
├── PayPal
└── BankTransfer

Exception Hierarchies:

Exception
├── IOException
│   ├── FileNotFoundException
│   └── SocketException
├── SQLException
│   ├── ConnectionException
│   └── QueryException
└── IllegalArgumentException

When NOT to Use Inheritance

  • When relationship is HAS-A, not IS-A
  • When subclass needs fundamentally different behavior
  • When hierarchy becomes too deep (3+ levels is a warning)
  • When you find yourself overriding most methods

Override vs Overload

Overriding and overloading are often confused. They are fundamentally different mechanisms with different purposes.

Method Overriding

Same method signature in subclass, replacing parent behavior:

public class Animal {
    public void speak() {
        System.out.println("Some generic sound");
    }
}

public class Dog extends Animal {
    @Override  // Annotation: signals intent to override
    public void speak() {
        System.out.println("Woof!");
    }
}

// Runtime polymorphism
Animal animal = new Dog();
animal.speak();  // Output: Woof! (not "Some generic sound")

Method Overloading

Same method name, different parameter lists (within the same class):

class Calculator {
    public int add(int a, int b) {
        return a + b;
    }
    
    public double add(double a, double b) {
        return a + b;
    }
    
    public int add(int a, int b, int c) {
        return a + b + c;
    }
}

// Compile-time polymorphism
Calculator calc = new Calculator();
calc.add(1, 2);       // Calls int version
calc.add(1.5, 2.5);   // Calls double version
calc.add(1, 2, 3);    // Calls 3-param version

Key Differences

Aspect Overriding Overloading
Where Subclass Same class (or parent)
Signature Must match Must differ
Binding Runtime (dynamic) Compile-time (static)
Purpose Specialize behavior Same operation, different inputs
@Override Recommended Not applicable
Return type Must be same/subtype Can differ
Access Cannot be more restrictive Can differ

Overriding Rules

public class Parent {
    protected void display() { }
}

public class Child extends Parent {
    // ✓ Can increase visibility
    @Override
    public void display() { }
    
    // ✗ Cannot decrease visibility
    // @Override
    // private void display() { }  // Compile error!
    
    // ✗ Cannot change return type (except covariant)
    // @Override
    // int display() { }  // Compile error!
    
    // ✓ Can return subtype (covariant return)
    @Override
    public Object clone() { return new Child(); }
}

Common Mistakes

  1. Forgetting @Override: Typos create new methods instead of overriding
  2. Overloading when overriding intended: Different signature = new method
  3. Changing semantics: Overriding should maintain the contract
  4. Calling parent method: Forget super.method() when you need parent behavior

Composition Over Inheritance

Composition over inheritance is a fundamental design principle: prefer containing objects over extending them.

The Problem with Deep Inheritance

         ┌─────────┐
         │ Vehicle │
         └────┬────┘
     ┌────────┴────────┐
┌────┴────┐      ┌─────┴────┐
│ Car     │      │ Truck    │
└────┬────┘      └──────────┘
     │
┌────┴──────────┐
│ SportsCar     │
└───────────────┘

Problems:
- Explosion of classes
- Tight coupling to parent
- Changes in parent affect all children
- Diamond problem in multiple inheritance

Composition Solution

// Instead of inheritance, compose behaviors
public class Vehicle {
    private Engine engine;
    private Transmission transmission;
    private GpsNavigator gps;
    
    public Vehicle(Engine engine, Transmission transmission, GpsNavigator gps) {
        this.engine = engine;
        this.transmission = transmission;
        this.gps = gps;
    }
    
    public void start() {
        engine.start();
        transmission.engage();
    }
}

// Flexible combinations
Vehicle sportsCar = new Vehicle(
    new V8Engine(),
    new ManualTransmission(),
    new PremiumGps()
);

Vehicle electricCar = new Vehicle(
    new ElectricEngine(),
    new NoTransmission(),
    new BasicGps()
);

HAS-A vs IS-A

IS-A (Inheritance)          HAS-A (Composition)
─────────────────          ──────────────────
Dog IS-A Animal             Car HAS-A Engine
Savings IS-A Account        Order HAS-A PaymentMethod
Square IS-A Shape           Server HAS-A Logger

Use when:                  Use when:
- Subtype relationship     - Assembling behaviors
- Subclass IS the parent   - Multiple capabilities
- Hierarchy is shallow     - Flexibility needed

Benefits of Composition

Benefit Explanation
Flexibility Change behavior at runtime
Testability Easy to mock dependencies
Loose coupling Components independent
Reuse Behaviors shared across unrelated classes
No hierarchy explosion Flat structure

Strategy Pattern with Composition

// Define behavior as interface
public interface SortStrategy {
    void sort(int[] array);
}

public class QuickSort implements SortStrategy {
    public void sort(int[] array) { /* quicksort */ }
}

public class MergeSort implements SortStrategy {
    public void sort(int[] array) { /* mergesort */ }
}

// Compose the behavior
public class Sorter {
    private SortStrategy strategy;
    
    public Sorter(SortStrategy strategy) {
        this.strategy = strategy;
    }
    
    public void sort(int[] array) {
        strategy.sort(array);
    }
    
    // Change behavior at runtime!
    public void setStrategy(SortStrategy strategy) {
        this.strategy = strategy;
    }
}

When to Use Inheritance

Despite composition being preferred, inheritance is still valid for:

  • Clear IS-A relationships
  • Framework requirements (extending framework classes)
  • Template Method pattern
  • Shallow hierarchies (1-2 levels)

Practice Problems

0/3solved
Design Inheritance System

Design a scalable Inheritance system. Cover high-level architecture, data model, and API design.

Solution
// Complete system design:
// - Functional + Non-functional requirements
// - Capacity estimation
// - Data model (SQL/NoSQL choice)
// - API endpoints
// - Component architecture
// - Scaling strategy
// - Monitoring & reliability
Inheritance Scaling

How would you scale Inheritance to handle 10x the current load? Identify bottlenecks and solutions.

Solution
// Scaling approach:
// 1. Load balancing
// 2. Database sharding/replication
// 3. Cache layer (Redis)
// 4. CDN for static assets
// 5. Async processing (queues)
// 6. Microservices decomposition
Inheritance Failure Modes

Analyze potential failure modes for Inheritance and design mitigation strategies.

Solution
// Failure mitigation:
// 1. Redundancy (multi-AZ)
// 2. Circuit breakers
// 3. Retry with backoff
// 4. Dead letter queues
// 5. Health checks
// 6. Graceful degradation

Quiz

1. What does the IS-A test check?

Question 1 options

2. What is the key difference between overriding and overloading?

Question 2 options

3. Why is composition preferred over inheritance?

Question 3 options

4. What is the Liskov Substitution Principle?

Question 4 options

5. When is inheritance still appropriate despite composition being preferred?

Question 5 options

Flashcards

Question

What is the IS-A relationship?

Answer

A relationship where a subclass is a specialized type of its superclass. Tested by asking 'Is [subclass] a [superclass]?' Dog IS-A Animal is valid.

Question

What is method overriding?

Answer

Redefining a parent class method in a subclass with the same signature. Uses @Override annotation. Enables runtime polymorphism.

Question

What is method overloading?

Answer

Multiple methods with the same name but different parameter lists in the same class. Resolved at compile-time. Enables same operation with different inputs.

Question

What is Composition over Inheritance?

Answer

A design principle preferring object composition (HAS-A) over class inheritance (IS-A). Provides flexibility, testability, and loose coupling.

Question

What is the Liskov Substitution Principle?

Answer

Objects of a subclass should be substitutable for objects of the superclass without breaking program correctness. Subclasses must honor the parent's contract.

Revision Notes

Key Takeaways

  • 1.Inheritance models IS-A relationships; composition models HAS-A
  • 2.Method overriding replaces parent behavior; overloading adds new signatures
  • 3.Composition provides more flexibility and testability than inheritance
  • 4.LSP requires subclasses to be substitutable for their base class
  • 5.Prefer composition unless there's a clear, shallow IS-A relationship

Interview Tips

  • Always test IS-A relationships: 'Dog IS-A Animal' is valid, 'Car IS-A Engine' is not
  • Mention LSP when discussing inheritance hierarchies in design
  • Show composition examples with strategy or dependency injection patterns
  • Discuss why deep inheritance (3+ levels) is often a design smell

Cheat Sheet

Inheritance - Cheat Sheet

IS-A Test:
"Is [subclass] a [superclass]?" ✓ Dog IS-A Animal
"Is [class] a [part]?" ✗ Car IS-A Engine → Use composition

Override vs Overload:

Override Overload
Location Subclass Same class
Signature Same Different
Binding Runtime Compile-time
@Override Yes N/A

LSP:
Subclasses must be substitutable for base class.
Ostrich extending Bird and throwing on fly() violates LSP.

Composition > Inheritance:

  • Flexibility: change behavior at runtime
  • Testability: easy to mock
  • Loose coupling: independent components
  • No hierarchy explosion

When to use inheritance:

  • Clear IS-A relationship
  • Shallow hierarchy (1-2 levels)
  • Framework requirements
  • Template Method pattern