Inheritance
Inheritance models is-a relationships: SavingsAccount is an Account, Truck is a Vehicle.
Introduction
Inheritance models is-a relationships: SavingsAccount is an Account, Truck is a Vehicle. Subclasses reuse and specialize superclass behavior — powerful but easy to misuse in LLD.
Interviewers watch for fragile base classes, deep hierarchies, and violated Liskov substitution. Prefer shallow inheritance for true subtype relationships; use composition when behavior is shared but identity is not is-a.
This lesson covers safe inheritance, method overriding, and when to stop subclassing.
Understanding the topic
Key concepts
- extends for class inheritance; implements for interface implementation.
- Superclass defines common state/behavior; subclass adds or overrides.
- @Override methods must honor superclass contracts (LSP).
- final prevents further subclassing when hierarchy should be closed.
- Protected access shares with subclasses only — use sparingly.
- Composition often replaces inheritance for code reuse without subtype coupling.
Step-by-step explanation
- Confirm is-a relationship with substitution test: can subclass stand in for parent?
- Extract common fields/methods to superclass or interface default methods.
- Override methods preserving invariants and expectations.
- Use super.method() when extending, not replacing, behavior.
- Limit depth — two levels usually enough in interviews.
- Consider sealed class hierarchies for controlled extension.
Informative example
Vehicle hierarchy for parking lot — subtype-specific spot matching:
public abstract class Vehicle {private final String licensePlate;protected Vehicle(String licensePlate) {this.licensePlate = Objects.requireNonNull(licensePlate);}public String licensePlate() { return licensePlate; }public abstract SpotType requiredSpotType();}public final class Car extends Vehicle {public Car(String licensePlate) { super(licensePlate); }@Overridepublic SpotType requiredSpotType() { return SpotType.COMPACT; }}public final class Truck extends Vehicle {public Truck(String licensePlate) { super(licensePlate); }@Overridepublic SpotType requiredSpotType() { return SpotType.LARGE; }}public enum SpotType { COMPACT, LARGE }
requiredSpotType() is the extension point — ParkingLot calls vehicle.requiredSpotType() without instanceof chains.
Real-world use
Real-world applications
- Vehicle types, account types, chess piece movements.
- Framework hooks (Template Method in servlet-style designs).
- Sealed domain hierarchies with exhaustive handling.
Best practices
- Favor composition when relationship is has-a.
- Keep superclass constructors simple and callable by subclasses.
- Document what subclasses may override.
- Avoid calling overridable methods from superclass constructor.
- Use interfaces for roles; inheritance for shared implementation sparingly.
Common mistakes
- Inheritance for code reuse only (Wrong IS-A).
- Breaking LSP — subclass throws where parent did not.
- God superclass accumulating unrelated methods.
- Deep trees (Animal → Mammal → Dog → …) hard to explain in interviews.
Advanced interview questions
Q1BeginnerWhen should you use inheritance?
Q2BeginnerWhat does @Override do?
Q3IntermediateWhat is the Liskov Substitution Principle?
Q4IntermediateInheritance vs interface implementation?
Q5AdvancedWhen would you use a sealed class hierarchy?
Summary
Use inheritance for genuine is-a relationships only. Override methods without breaking superclass contracts. Keep hierarchies shallow and domain-aligned. Prefer composition when reuse does not imply subtype. Sealed types control extension in Java 21.