Back to the map

Module 20 · OOP fundamentals · Lesson 3 of 5

Inheritance

A subclass reuses and specialises a parent class. Overriding and super, constructor chaining, single versus multiple inheritance and the diamond problem, and the fragile base class problem that makes composition the safer default.

CoreMust-knowJunior 12 min Updated Nov 2026

Why it matters

Inheritance is the most visible feature of object-oriented languages and the most overused. Used well, it models genuine "is a kind of" relationships and lets frameworks call your code through a common type. Used as a shortcut for reusing code, it welds classes together so that a change in a parent silently breaks children. Knowing exactly what inheritance couples together is what lets you choose it deliberately, and recognise when composition is the better tool.

The idea

A subclass (child) extends a superclass (parent): it inherits the parent's fields and methods, can add its own, and can override inherited methods to specialise behaviour. Anywhere the parent type is expected, a child object can be used. That substitutability is the point; code reuse is a side effect.

Notifiersend(message) · format()EmailNotifiersend: SMTPSmsNotifiersend: SMS gatewayPushNotifiersend: push servicearrows point from child to parent: 'is a'
A small hierarchy. Each subclass is a Notifier and can be used wherever one is expected; each overrides send with its own delivery.

Overriding, super and constructors

An overriding method replaces the parent's version for objects of the child class, and the choice is made at runtime by the object's actual class (the polymorphism lesson explains how). The child can still call the parent's version with super.method() to extend rather than replace it. Constructors are not inherited: a child's constructor must call a parent constructor first, so the parent part of the object is initialised before the child part, a sequence called constructor chaining. Calling overridable methods from a constructor is a classic trap, because the child's override runs before the child's own fields are initialised.

abstract class Notifier {
    public final void notify(User u, String msg) {    // fixed algorithm
        String text = format(msg);
        send(u, text);
    }
    protected String format(String msg) { return msg.strip(); }
    protected abstract void send(User u, String text); // each subclass decides
}

class SmsNotifier extends Notifier {
    @Override protected String format(String msg) {
        return super.format(msg).substring(0, Math.min(160, msg.length())); // extend parent
    }
    @Override protected void send(User u, String text) { gateway.sms(u.phone(), text); }
}

Single, multiple and the diamond

Java and C# allow a class one parent (single inheritance) but many interfaces. C++ and Python allow multiple parents, which raises the diamond problem: if B and C both extend A and override the same method, and D extends both, which version does D get? C++ requires explicit resolution or virtual inheritance; Python resolves by a defined method resolution order. Languages with single inheritance plus interfaces (and default methods) avoid most of the ambiguity while keeping multiple types.

The fragile base class problem

A subclass depends not just on its parent's public methods but on how they are implemented. Suppose a subclass of a collection counts insertions by overriding both add and addAll. If the parent's addAll is implemented by calling add for each element, every bulk insert is counted twice; if the parent later changes addAll to stop calling add, the count silently becomes correct, or wrong in a new way. The subclass broke, or changed meaning, without its own code changing. That is the fragile base class problem: inheritance exposes implementation details across the class boundary.

 InheritanceComposition
Relationshipis a kind ofhas a, or uses a
CouplingChild depends on parent's implementation detailsHolder depends only on the part's public interface
FlexibilityFixed at compile timeCan swap the part at runtime
ReuseAll of the parent, wanted or notExactly the behaviour you delegate to
Use whenGenuine subtype; framework requires it; the parent is designed for extensionMost other reuse

The Low-Level Design track's lesson on composition over inheritance takes this further. A useful rule from both tracks: design classes either for inheritance (documented extension points, final elsewhere) or prohibit it (mark them final or sealed).

Trade-offs

Inheritance gives substitutability and concise reuse for genuine hierarchies, and costs tight coupling, deep hierarchies that are hard to follow, and fragility when parents change. Composition costs a little more code (delegating methods) and gives loose coupling and runtime flexibility. Shallow hierarchies built around abstract parents designed for extension are the healthy middle.

In an interview

Name the relationship before drawing the arrow: "Every vehicle type is a Vehicle the lot can park, so Car, Bike and Truck extend Vehicle and override size; but pricing varies independently, so the lot holds a PricingStrategy instead of subclassing for each pricing rule." Choosing inheritance for one axis and composition for another is exactly what interviewers look for.

Check yourself

A CountingSet subclass overrides add (count++ then super.add) and addAll (count += n then super.addAll). Adding a list of 3 items with addAll reports a count of 6. Why?

Inheritance · Backplane