Overview
Inheritance lets a class (subclass) reuse and extend another class (superclass). The subclass gets the parent's fields and methods and can override behavior. It models an 'is-a' relationship: a SavingsAccount is an Account.
Inheritance is powerful but creates the tightest coupling in OOP: subclasses depend on the parent's internals, and changes to the parent can break children (the fragile base class problem). Modern guidance is to inherit only for true is-a relationships that honor the parent's contract (Liskov Substitution), and to prefer composition for sharing behavior.
Children inherit traits from parents automatically, including ones they may not want. You cannot pick only the useful traits, which is why inheritance should be used carefully.
When to use it
- A genuine is-a relationship with a stable base contract.
- Framework extension points designed for subclassing (Template Method).
- Sharing implementation among closely related types in one module.
Where it shows up in interviews
Recognize it when: vehicle types, account types, shapes, chess pieces.
- Design a chess game
- Design a parking lot vehicle hierarchy
Recognize it when: subclass disables or throws in parent methods.
- Refactor a Square extends Rectangle
- Refactor a deep class hierarchy
Where it is used in real software
IOException extends Exception extends Throwable, letting catch blocks handle families of errors.
Android views and older Java Swing components are built on inheritance hierarchies.
Spring's AbstractController and JUnit extensions use abstract base classes as extension points.
Key terms
- Superclass / subclass
- Parent / child in the hierarchy.
- Override
- Replace a parent method's behavior.
- super
- Call the parent's constructor or method.
- Fragile base class
- Parent changes unexpectedly break subclasses.
- final / sealed
- Prevent or restrict further subclassing.
How it works, step by step
- 1Check the is-a test
Is every child truly usable wherever the parent is?
- 2Put shared stable behavior in the parent
Keep it small.
- 3Override deliberately
Use @Override / override keyword to catch mistakes.
- 4Protect the contract
Children must not weaken guarantees.
- 5Limit depth
Keep hierarchies shallow (one or two levels).
Chess pieces
abstract class Piece { canMove(from, to, board) }
| Subclass | Overrides canMove as | Shares from Piece |
|---|---|---|
| Rook | Straight lines, path clear | color, position, capture rules |
| Bishop | Diagonals, path clear | color, position, capture rules |
| Knight | L-shape, can jump | color, position, capture rules |
| King | One square any direction | color, position, capture rules |
NOWSubclass: Rook | Overrides canMove as: Straight lines, path clear | Shares from Piece: color, position, capture rules
A legitimate hierarchy: every piece is a Piece, and the board treats them uniformly through canMove.
Implementation
type Pos = { r: number; c: number }; abstract class Piece { constructor(readonly color: "white" | "black") {} abstract canMove(from: Pos, to: Pos): boolean; protected delta(from: Pos, to: Pos) { return { dr: Math.abs(to.r - from.r), dc: Math.abs(to.c - from.c) }; }} class Knight extends Piece { canMove(from: Pos, to: Pos) { const { dr, dc } = this.delta(from, to); return (dr === 2 && dc === 1) || (dr === 1 && dc === 2); }} class King extends Piece { canMove(from: Pos, to: Pos) { const { dr, dc } = this.delta(from, to); return Math.max(dr, dc) === 1; }} const pieces: Piece[] = [new Knight("white"), new King("black")];pieces.map((p) => p.canMove({ r: 0, c: 0 }, { r: 1, c: 2 })); // [true, false]Complexity and performance
Virtual call.
Child sees parent internals.
Trade-offs
Inheritance reuses code easily but ties children to the parent's implementation details.
A class's parent is fixed at compile time; composition can swap behavior at runtime.
Variants and related techniques
Inherit a contract without implementation; much looser coupling.
Java sealed classes and TypeScript discriminated unions close the set of subtypes.
Common mistakes
- Inheriting to reuse a helper method.
Fix: Use composition or a utility instead.
- Subclasses that throw UnsupportedOperationException.
Fix: The child is not really an is-a; redesign the hierarchy.
- Deep hierarchies.
Fix: Flatten; favor interfaces and composition.
Interview questions
When should you use inheritance?
When there is a true is-a relationship, the subclass can be used anywhere the parent is expected without surprises, and the parent is designed for extension. Otherwise prefer composition.
What is the fragile base class problem?
Changes in a parent class, even internal ones, can break subclasses that depend on its behavior or call order, because subclasses are coupled to the parent's implementation.
Practice problems
| Problem | Difficulty | What it trains |
|---|---|---|
| Model vehicle types for a parking lot | Easy | Is-a checks. |
| Refactor Square extends Rectangle | Medium | LSP violation. |