Skip to main content

Inheritance

Inheritance is building a new class on an existing one, mostly to reuse code. The bargain has a price: a subclass inherits the entire interface of its parent, not a menu it gets to pick from, and it cannot hand any part of that back.

What breaks without it

Without inheritance, every class that needs behavior another class already has must either duplicate that code or delegate to it by hand at every call site. A codebase with Invoice and ExpenseReport as unrelated classes, both needing save(), print() and a createdAt timestamp, either copies that logic twice - and now a bug fix has to land in two places and someone will forget the second one - or wires up manual delegation everywhere, which is inheritance's job done worse and by hand. Inheritance also gives you substitutability for free: code written against Animal works on any Animal subclass without modification, which is the foundation polymorphism is built on.

Worked example: reuse across siblings

A reporting tool models Invoice and ExpenseReport as subclasses of an abstract Document. Both inherit save(), print() and a createdAt timestamp for free, and each adds its own method: Invoice gets computeTax(), ExpenseReport gets flagOverBudget().

«abstract»Document- createdAt: datetime+ save()+ print()Invoice+ computeTax()ExpenseReport+ flagOverBudget()
Both subclasses inherit save, print and createdAt for free, and add exactly one method of their own.

The reuse is real, but so is the bargain: both subclasses are now stuck honoring every method Document declares, whether or not it makes sense for them. If a DraftMemo subclass is added later that should never actually be printed, silently no-op'ing print() hides a real constraint from any caller that reasonably expects print() to do something - a sign the hierarchy itself needs rethinking, not that DraftMemo should quietly break the contract it inherited.

Worked example: one superclass, several interfaces

A class can extend only one superclass, but implement as many interfaces as it needs. Cat extends Animal for its shared, concrete behavior, and separately implements FourLegged and OxygenBreather for capabilities that have nothing to do with being an Animal specifically.

AnimalCat«interface»FourLegged+ run(destination)«interface»OxygenBreather+ breathe()
One superclass, any number of interfaces.

Why not multiple class inheritance

Imagine a language allowed a class to extend two concrete parents, and a hypothetical FlyingCar extended both Car and Airplane - both of which define refuel(), with different logic.

Car+ refuel()Airplane+ refuel()FlyingCarBoth parents definerefuel() differently -which one doesFlyingCar inherit?
This exact shape is forbidden in most class-based languages - there is no principled answer to which refuel() wins.

There is no principled rule for which refuel() FlyingCar should get - this is the diamond problem, and it's the reason most class-based languages cap concrete inheritance at one parent. Interfaces sidestep the problem entirely: FourLegged and OxygenBreather carry no competing implementation, so there is nothing for two of them to collide over.

Language mechanics: extends vs implements

extends inherits a real, concrete implementation - fields, method bodies, constructors - from exactly one parent. implements promises to fulfill a contract with no inherited code at all; a class can implement as many interfaces as it wants because promises don't collide the way concrete method bodies do.

Overriding rules: a subclass overriding a method must keep the same signature (same name, same or narrower parameter types depending on the language), cannot reduce the method's visibility (a public method can't become protected in a subclass), and may return a covariant type - a narrower return type than the parent declared, since anything expecting the parent's return type can still accept the more specific one.

super calls: a subclass can call super.methodName() to invoke the parent's version of an overridden method explicitly - useful when the subclass wants to extend the parent's behavior rather than replace it outright, for example logging before calling super.save() to still get the base save logic.

Constructor chaining: a subclass constructor always invokes a superclass constructor before running its own body - explicitly with super(...), or implicitly, the language calling the parent's no-argument constructor if you don't write the call yourself. This guarantees the inherited part of the object is fully initialized before the subclass adds anything on top:

class Employee {
String name;
String id;
Employee(String name, String id) {
this.name = name;
this.id = id;
}
}
 
class Manager extends Employee {
int teamSize;
Manager(String name, String id, int teamSize) {
super(name, id); // Employee's constructor runs first
this.teamSize = teamSize;
}
}

Abstract classes vs concrete classes: an abstract class can declare methods with no body (the subclass must fill them in) alongside methods with real implementations, and it cannot be instantiated directly - new Document() is not legal if Document is abstract, only new Invoice() or new ExpenseReport(). A concrete class must implement every abstract method it inherits before it can be instantiated at all.

Common mistakes:

  • Reaching for inheritance purely to reuse code when there is no real is-a relationship - making ExpenseReport extend Invoice just because both happen to have a total field and a save() method, when neither is actually a kind of the other. The fields matching is a coincidence, not a taxonomy.
  • Building deep inheritance chains - four or five levels of subclassing to reuse a sliver of behavior at each level makes it genuinely hard to answer "where does this method actually come from" without reading every ancestor. Once a hierarchy gets that deep, composition (holding a reference to an object and delegating to it) usually expresses the same reuse with a much shorter chain to trace.

Where this shows up in the patterns catalogue

  • Template Method is inheritance doing the deciding: the base class fixes the order of operations, and subclasses fill in individual steps by overriding them.
  • Favoring composition once a hierarchy gets deep is the whole argument behind Decorator and Strategy - both add or swap behavior by holding a reference to another object instead of adding another layer of subclass.

Try it yourself: Document currently declares print() for every subclass - work through what a DraftMemo subclass that is never meant to be printed should do: inherit print() anyway and hope nobody calls it, override it to throw, or restructure the hierarchy so printability isn't assumed for every Document. Then take the Employee / Manager chain above and add a third level, RegionalManager extends Manager - write out what its constructor has to call, in what order, for all three levels to end up correctly initialized.

Check yourself

Question 1 of 4

Cat implements both FourLegged and OxygenBreather, but extends only Animal. Why the asymmetry?