9 min read
Object-Oriented Design Index
Tier 1 -- Foundations
Core Concepts
SOLID Principles
Creational Patterns
Structural Patterns
Tier 2
Tier 3
Tier 4
Tier 5
Object-Oriented Design Index
Tier 1 -- Foundations
Core Concepts
SOLID Principles
Creational Patterns
Structural Patterns
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.
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?
2. How It Works
Step-by-step mechanics:
- Define a superclass with shared and behavior.
- Declare the subclass, naming the superclass as its parent.
- The subclass automatically inherits all non-private members.
- Use
super()to call the parent constructor or a parent method. - Use
@override/@Overrideto replace parent behavior with specialized behavior. - 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
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?
3. Variants & Comparisons
Flavors of Inheritance
| Approach | How It Works | Pros | Cons | Best For |
|---|---|---|---|---|
| Single inheritance | One parent class | Simple, no ambiguity | Can't reuse from multiple hierarchies | Most everyday cases |
| Multi-level inheritance | C extends B extends A | Natural depth (GuideDog → Dog → Animal) | Deep chains become brittle | Genuinely hierarchical domains |
| Multiple inheritance | class C(A, B) (Python only) | Reuse from multiple sources | Diamond problem; MRO complexity | Mixins in Python |
| Interface-based (Java) | implements A, B | Type-safe multiple contracts, no diamond | No inherited implementation (pre-Java 8) | Capability contracts |
| Abstract class | Parent has both concrete + abstract methods | Partial implementation shared | Still single-parent in Java | Template for subclasses |
Python vs. Java Mechanics
| Feature | Python | Java |
|---|---|---|
| Inheritance syntax | class Child(Parent): | class Child extends Parent |
| Multiple class inheritance | Yes (class C(A, B):) | No (interfaces only) |
| Call parent constructor | super().__init__(...) | super(...) — must be first line |
| Override annotation | @override (Python 3.12+, optional) | @Override (compiler-checked) |
| Abstract class | from abc import ABC, abstractmethod | abstract class keyword |
| Interface equivalent | ABC + @abstractmethod | interface keyword |
| Type check | isinstance(obj, ClassName) | obj instanceof ClassName |
| Diamond resolution | C3 MRO linearization | Not 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 .
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?
4. When to Use It (and When NOT To)
Use Inheritance When:
- A true IS-A relationship exists:
GuideDogis genuinely aDog;Dogis genuinely anAnimal. - Subclasses share significant behavior but need to specialize part of it.
- You want polymorphic substitution: code written against
Animalshould work for any subclass. - An can provide a template (e.g., 80% shared implementation, 20% abstract hooks).
Do NOT Use Inheritance When:
| Anti-Pattern | Problem | Fix |
|---|---|---|
| Inheritance for code reuse only | Stack extends Vector (Java legacy) violates IS-A — a Stack is not a Vector | Use composition: Stack has-a internal List |
| Deep chains (>3 levels) | Changes at the root break all descendants; hard to trace behavior | Flatten with composition or interfaces |
| Overriding to weaken contracts | Subclass throws UnsupportedOperationException for inherited methods | Violates LSP; use a narrower interface instead |
| Concrete class inheritance to inject behavior | Runtime surprises when parent logic changes | Use 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).
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?
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.
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?
6. Interview Cheat Sheet
Key Sentences to Demonstrate Deep Understanding
- " 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."
- "Every override should honor the parent's contract — if a subclass weakens preconditions or strengthens postconditions, it violates Liskov and breaks polymorphic substitution."
- "Java avoids the diamond problem by restricting classes to single ; Python resolves it with C3 MRO linearization, which produces a deterministic method resolution order."
- "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."
- "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
| Question | Concise 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.