Inheritance

9 min read

Reading Progress0%
Object-Oriented Design Index
Tier 1 -- Foundations
Tier 2
Tier 3
Tier 4
Tier 5
Object-Oriented Design Index
Tier 1 -- Foundations
Tier 2
Tier 3
Tier 4
Tier 5

Inheritance

1. What Is It?

is the OOP mechanism by which a class (the subclass or child) derives fields, methods, and behavior from another class (the superclass or parent). The subclass gets everything the superclass defines and can either extend it with new members or override existing behavior to specialize it.

Without , every class that shares common behavior must duplicate that behavior. A Dog class and a Cat class would each carry identical eat(), sleep(), and breathe() implementations. Inheritance eliminates that duplication by placing shared logic in an Animal parent and letting both subclasses inherit it automatically — changes in one place propagate everywhere.


QUICK CHECK

A backend system has a EmailNotification class and a SMSNotification class. Both classes contain identical logTimestamp(), retryOnFailure(), and formatRecipient() methods. A developer wants to eliminate this duplication. Which approach best applies the principle of inheritance to solve this problem?

Choose one answer

2. How It Works

Step-by-step mechanics:

  1. Define a superclass with shared and behavior.
  2. Declare the subclass, naming the superclass as its parent.
  3. The subclass automatically inherits all non-private members.
  4. Use super() to call the parent constructor or a parent method.
  5. Use @override / @Override to replace parent behavior with specialized behavior.
  6. The subclass IS-A superclass — it can be used anywhere the superclass is expected.

Python:

from abc import ABC, abstractmethod


class Animal(ABC):
    def __init__(self, name: str, age: int) -> None:
        self._name = name  # protected by convention
        self._age = age

    def eat(self) -> None:
        print(f"{self._name} is eating.")

    def sleep(self) -> None:
        print(f"{self._name} is sleeping.")

    @abstractmethod
    def make_sound(self) -> str:
        """Subclasses must implement their own sound."""
        ...

    def __str__(self) -> str:
        return f"{type(self).__name__}(name={self._name}, age={self._age})"


class Dog(Animal):
    def __init__(self, name: str, age: int, breed: str) -> None:
        super().__init__(name, age)  # Python 3 no-argument super()
        self._breed = breed

    def make_sound(self) -> str:
        return "Woof!"

    def fetch(self, item: str) -> None:
        print(f"{self._name} fetches the {item}.")


class Cat(Animal):
    def __init__(self, name: str, age: int, is_indoor: bool) -> None:
        super().__init__(name, age)
        self._is_indoor = is_indoor

    def make_sound(self) -> str:
        return "Meow!"

    def purr(self) -> None:
        print(f"{self._name} purrs contentedly.")


class GuideDog(Dog):
    """Multi-level inheritance: GuideDog → Dog → Animal."""

    def __init__(self, name: str, age: int, breed: str, owner_name: str) -> None:
        super().__init__(name, age, breed)
        self._owner_name = owner_name

    def guide(self) -> None:
        print(f"{self._name} guides {self._owner_name} safely.")


# Usage
dog = Dog("Rex", 3, "Labrador")
cat = Cat("Whiskers", 5, is_indoor=True)
guide_dog = GuideDog("Buddy", 4, "Golden Retriever", "Alice")

for animal in [dog, cat, guide_dog]:
    animal.eat()
    print(animal.make_sound())

print(isinstance(guide_dog, Dog))    # True — GuideDog IS-A Dog
print(isinstance(guide_dog, Animal)) # True — GuideDog IS-A Animal

Java:

public abstract class Animal {
    private final String name;
    private final int age;

    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void eat() {
        System.out.println(name + " is eating.");
    }

    public void sleep() {
        System.out.println(name + " is sleeping.");
    }

    // Subclasses must provide their own sound
    public abstract String makeSound();

    protected String getName() { return name; }
    protected int getAge()     { return age; }

    @Override
    public String toString() {
        return getClass().getSimpleName() + "(name=" + name + ", age=" + age + ")";
    }
}

public class Dog extends Animal {
    private final String breed;

    public Dog(String name, int age, String breed) {
        super(name, age);  // must be first statement
        this.breed = breed;
    }

    @Override
    public String makeSound() {
        return "Woof!";
    }

    public void fetch(String item) {
        System.out.println(getName() + " fetches the " + item + ".");
    }
}

public class Cat extends Animal {
    private final boolean isIndoor;

    public Cat(String name, int age, boolean isIndoor) {
        super(name, age);
        this.isIndoor = isIndoor;
    }

    @Override
    public String makeSound() {
        return "Meow!";
    }

    public void purr() {
        System.out.println(getName() + " purrs contentedly.");
    }
}

public class GuideDog extends Dog {
    private final String ownerName;

    public GuideDog(String name, int age, String breed, String ownerName) {
        super(name, age, breed);
        this.ownerName = ownerName;
    }

    public void guide() {
        System.out.println(getName() + " guides " + ownerName + " safely.");
    }
}

// Usage
Animal[] animals = {
    new Dog("Rex", 3, "Labrador"),
    new Cat("Whiskers", 5, true),
    new GuideDog("Buddy", 4, "Golden Retriever", "Alice")
};

for (Animal animal : animals) {
    animal.eat();
    System.out.println(animal.makeSound());
}

GuideDog gd = new GuideDog("Buddy", 4, "Golden Retriever", "Alice");
System.out.println(gd instanceof Dog);    // true
System.out.println(gd instanceof Animal); // true

QUICK CHECK

A backend developer is building a PremiumUser class that extends a User class. The User constructor requires username and email parameters, and PremiumUser adds a subscriptionTier field. Which of the following correctly describes how PremiumUser's constructor should be structured in both Python and Java?

Choose one answer

3. Variants & Comparisons

Flavors of Inheritance

ApproachHow It WorksProsConsBest For
Single inheritanceOne parent classSimple, no ambiguityCan't reuse from multiple hierarchiesMost everyday cases
Multi-level inheritanceC extends B extends ANatural depth (GuideDog → Dog → Animal)Deep chains become brittleGenuinely hierarchical domains
Multiple inheritanceclass C(A, B) (Python only)Reuse from multiple sourcesDiamond problem; MRO complexityMixins in Python
Interface-based (Java)implements A, BType-safe multiple contracts, no diamondNo inherited implementation (pre-Java 8)Capability contracts
Abstract classParent has both concrete + abstract methodsPartial implementation sharedStill single-parent in JavaTemplate for subclasses

Python vs. Java Mechanics

FeaturePythonJava
Inheritance syntaxclass Child(Parent):class Child extends Parent
Multiple class inheritanceYes (class C(A, B):)No (interfaces only)
Call parent constructorsuper().__init__(...)super(...) — must be first line
Override annotation@override (Python 3.12+, optional)@Override (compiler-checked)
Abstract classfrom abc import ABC, abstractmethodabstract class keyword
Interface equivalentABC + @abstractmethodinterface keyword
Type checkisinstance(obj, ClassName)obj instanceof ClassName
Diamond resolutionC3 MRO linearizationNot applicable (no multi-class)

The Diamond Problem (Python)

class A:
    def greet(self) -> str:
        return "Hello from A"

class B(A):
    def greet(self) -> str:
        return "Hello from B"

class C(A):
    def greet(self) -> str:
        return "Hello from C"

class D(B, C):  # Diamond: D → B → C → A
    pass

print(D.__mro__)  # (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)
print(D().greet())  # "Hello from B" — MRO picks B first

Python resolves the diamond via C3 linearization (MRO). Java avoids it entirely by prohibiting multiple class .


QUICK CHECK

A Python backend service defines the following class hierarchy: Serializable and Loggable are both independent classes, and APIResponse inherits from both (class APIResponse(Serializable, Loggable)). Both Serializable and Loggable define a format() method. When APIResponse().format() is called, which class's format() method is invoked, and why?

Choose one answer

4. When to Use It (and When NOT To)

Use Inheritance When:

  • A true IS-A relationship exists: GuideDog is genuinely a Dog; Dog is genuinely an Animal.
  • Subclasses share significant behavior but need to specialize part of it.
  • You want polymorphic substitution: code written against Animal should work for any subclass.
  • An can provide a template (e.g., 80% shared implementation, 20% abstract hooks).

Do NOT Use Inheritance When:

Anti-PatternProblemFix
Inheritance for code reuse onlyStack extends Vector (Java legacy) violates IS-A — a Stack is not a VectorUse composition: Stack has-a internal List
Deep chains (>3 levels)Changes at the root break all descendants; hard to trace behaviorFlatten with composition or interfaces
Overriding to weaken contractsSubclass throws UnsupportedOperationException for inherited methodsViolates LSP; use a narrower interface instead
Concrete class inheritance to inject behaviorRuntime surprises when parent logic changesUse composition + delegation

Decision Triggers

  • "These two classes share AND behavior, and one genuinely is a kind of the other" → use .
  • "I just want to reuse a method" → use .
  • "Multiple unrelated classes need the same capability" → use an .
  • "I need to swap implementations at runtime" → use with an ( pattern).

QUICK CHECK

A developer is building a Stack data structure and considers extending an existing ArrayList class so they can reuse its add() and remove() methods without rewriting them. What is the primary problem with this design, and what is the recommended fix?

Choose one answer

5. Real-World Usage

1. Java Collections Framework

java.util.ArrayList extends java.util.AbstractList (which provides default implementations of most List methods) and implements java.util.List, RandomAccess, Cloneable, and Serializable. This is a textbook use of the + pattern: AbstractList handles boilerplate so concrete implementations (ArrayList, LinkedList) only override the performance-critical methods.

2. Python's collections.abc Module

Python's collections.abc module (moved from collections in Python 3.3) provides abstract base classes like Iterable, Sequence, Mapping, and MutableMapping. Custom collections inherit from these ABCs, get default implementations for derived methods (e.g., __contains__ from Iterable), and register as the correct type for isinstance() checks — all through .

3. Java AWT/Swing GUI Hierarchy

Swing's component hierarchy (JComponent → Container → Component → Object) demonstrates multi-level in practice. Every widget inherits paint, layout, and event-handling infrastructure from Component while specialized widgets (JButton, JTextField) override only what they need. The polymorphic model lets layout managers treat all components uniformly via the Component reference.


QUICK CHECK

In the Java Collections Framework, ArrayList extends AbstractList rather than directly implementing all methods of the List interface from scratch. What is the primary benefit of this abstract class + interface pattern?

Choose one answer

6. Interview Cheat Sheet

Key Sentences to Demonstrate Deep Understanding

  1. " models an IS-A relationship — I use it when the subclass genuinely is a specialized version of the parent, not just when I want to reuse code."
  2. "Every override should honor the parent's contract — if a subclass weakens preconditions or strengthens postconditions, it violates Liskov and breaks polymorphic substitution."
  3. "Java avoids the diamond problem by restricting classes to single ; Python resolves it with C3 MRO linearization, which produces a deterministic method resolution order."
  4. "Inheritance couples the subclass tightly to the parent's internals — gives me reuse with looser , so I default to unless a true IS-A exists."
  5. "Abstract classes let me provide a partial implementation and enforce a contract; I use them when subclasses share code, and interfaces when they only share a type contract."

Common Follow-Up Questions

QuestionConcise Answer
"What's the difference between abstract classes and interfaces?"Abstract classes share implementation + enforce contracts; interfaces define only contracts (pre-Java 8). Use abstract class for IS-A with shared state; interface for capability contracts across unrelated types.
"When does inheritance violate LSP?"When a subclass overrides a method in a way that breaks the parent's expected behavior — e.g., throwing where the parent doesn't, or silently ignoring an operation the parent performs.
"How does Python's MRO work?"C3 linearization: Python builds a deterministic order by merging parent MROs left-to-right, never revisiting a class until all its descendants have been processed. ClassName.__mro__ shows the result.
"Can you have too much inheritance?"Yes — chains deeper than ~3 levels become hard to reason about. Prefer flatter hierarchies with composition.
"What's the difference between overriding and overloading?"Overriding replaces a parent method with the same signature at runtime (polymorphism). Overloading defines multiple methods with the same name but different parameter lists (resolved at compile time in Java; Python doesn't have true overloading).

Connections to Other Concepts

  • — the payoff of inheritance: code written against the supertype works for all subtypes.
  • Principle (LSP) — the rule that governs correct inheritance: subclasses must be substitutable for their parent without altering correctness.
  • Open/Closed Principle (OCP)inheritance enables extension (add a subclass) without modification (don't touch the parent).
  • Pattern — a direct application of inheritance: parent defines the algorithm skeleton, subclasses fill in the abstract steps.
  • Composition vs. Inheritance — the perennial trade-off: inheritance for IS-A and ; composition for HAS-A and flexibility.
Glossary History

Click dotted jargon to save explanations here.

Glossary History

Click dotted jargon to save explanations here.