OOD Fundamentals
SOLID Principles
Creational Patterns
Structural Patterns
Behavioral Patterns
Classic OOD Problems: Part 1
Classic OOD Problems: Part 2
Inheritance and Polymorphism
Inheritance is the most overused feature in OOP. Before you learn the mechanics, you need to understand why that statement is true: and why inheritance still matters when used correctly.
The core idea is simple: a child class extends a parent class, inheriting all of the parent's fields and methods. You write the shared behavior once in the parent, and every child gets it for free. This is the "is-a" relationship. A Dog is-a Animal. A SavingsAccount is-a BankAccount. A Manager is-a Employee.

Why Inheritance Exists
Without inheritance, you would copy-paste shared behavior into every class. Ten animal types that all need a name field and an eat() method means ten copies of the same code. When you fix a bug in eat(), you fix it in ten places. Inheritance eliminates this duplication by centralizing shared behavior in a parent class.
Dog and Cat both inherit name and eat() from Animal. They add their own speak() behavior. If you fix a bug in eat(), every animal type gets the fix automatically.
The Is-A Test
Before creating an inheritance relationship, apply the is-a test rigorously. Ask: "Is every instance of the child truly an instance of the parent, in every context?" A Circle is-a Shape: yes. A Penguin is-a Bird: tricky, because Bird might have a fly() method that penguins cannot use.
The is-a test is not about real-world taxonomy. It is about behavioral compatibility. In the real world, a square is-a rectangle. In code, Square breaks Rectangle's contract because setting width on a square also changes height. The lesson: inheritance models behavioral relationships, not biological or mathematical ones.
What Gets Inherited
A child class inherits everything from its parent:
- Fields: instance variables defined in the parent become part of the child
- Methods: all public and protected methods are available on the child
- Constructors: the parent's constructor must be called (explicitly or implicitly) before the child's constructor runs
What does not get inherited depends on the language. In Java, private fields exist in the child object's memory but cannot be accessed directly: you use getters. In Python, name-mangled attributes (prefixed with __) are technically accessible but conventionally private.
Notice that Manager calls super(name, baseSalary) to initialize the parent's fields. The parent's constructor runs first, setting up the inherited state. Then the child's constructor adds its own state (bonus). This ordering is mandatory: you cannot use inherited fields before they are initialized.
Inheritance creates tight coupling between parent and child. Every change to the parent's public or protected interface affects every child class. A method added to Animal affects Dog, Cat, Bird, and every other subclass. Before you create an inheritance hierarchy, ask yourself: will the parent's behavior remain stable? If the parent changes frequently, you are propagating instability to every child.
Single vs Multiple Inheritance
Most languages allow single inheritance only: a class extends exactly one parent. Java, C#, and Ruby follow this model. Python and C++ allow multiple inheritance, where a class can extend two or more parents.
Multiple inheritance introduces the diamond problem. If class D extends both B and C, and both B and C extend A, which version of A's methods does D get? Python resolves this with Method Resolution Order (MRO), which linearizes the hierarchy into a predictable lookup chain. C++ requires explicit disambiguation. Java avoids the problem entirely by limiting inheritance to one class while allowing multiple interfaces.
The diamond problem is not just academic. It causes real bugs in production code when two parent classes define methods with the same name but different behavior. This is one of the reasons most modern language designers restrict or discourage multiple inheritance.