Complete Guide | Lesson 4: Classes → Lesson 5: Inheritance & Composition → Lesson 6: Interfaces (Apr 30)
Inheritance is the first superpower every developer learns. It's also the first one they abuse.
The pitch is seductive: define behavior once in a parent class, and every child class gets it for free. A Dog extends Animal. A Sedan extends Vehicle. A CheckingAccount extends BankAccount. Write the shared behavior once, share it everywhere, never repeat yourself. What could go wrong?
Quite a lot, actually. And this lesson exists because understanding when NOT to use inheritance is more valuable than understanding how to use it.
This is Lesson 5, and it maps to Binah on the Tree of Life. Binah means "Understanding." Where Chokmah (Lesson 4) was the flash of creative insight that produced blueprints, Binah is the receptive form that gives structure and limitation. Binah says: here are the rules. Here are the constraints. Here is where one thing ends and another begins. In programming, Binah teaches you relationships between classes: when one thing truly IS another thing, and when it merely HAS another thing. Getting this distinction right is the difference between code that scales and code that crumbles.
The Family Tree
Inheritance creates a family tree of classes. A parent class (also called a base class or superclass) defines shared properties and methods. Child classes (subclasses) inherit everything from the parent and can add their own.
1CLASS Animal
2 name: String
3 age: Integer
4
5 METHOD eat()
6 PRINT this.name + " is eating"
7 END METHOD
8
9 METHOD sleep()
10 PRINT this.name + " is sleeping"
11 END METHOD
12END CLASS
13
14CLASS Dog EXTENDS Animal
15 breed: String
16
17 METHOD bark()
18 PRINT this.name + " says: Woof!"
19 END METHOD
20
21 METHOD fetch(item)
22 PRINT this.name + " fetches the " + item
23 END METHOD
24END CLASS
25
26CLASS Cat EXTENDS Animal
27 isIndoor: Boolean
28
29 METHOD purr()
30 PRINT this.name + " is purring"
31 END METHOD
32
33 METHOD ignoreOwner()
34 PRINT this.name + " stares at you with contempt"
35 END METHOD
36END CLASSDog inherits eat() and sleep() from Animal without rewriting them. It adds bark() and fetch() on its own. Cat inherits the same base methods but adds purr() and ignoreOwner(). (The cat one is the most realistic method in this entire series.)
The IS-A Test
Before you write class X extends Y, ask yourself: "Is X genuinely a type of Y?"
- A Dog IS an Animal. Valid.
- A CheckingAccount IS a BankAccount. Valid.
- A Car IS an Engine. Invalid. A car HAS an engine. Different relationship entirely.
- A Square IS a Rectangle. Dangerous. Mathematically true. Programmatically, it violates Liskov Substitution (more on that in Lesson 7).
If the IS-A test fails, inheritance is the wrong tool. Use composition instead.
The Fragile Base Class Problem
Here's where the fairy tale ends. Inheritance creates coupling between parent and child classes. Every child depends on every detail of its parent. Change the parent, and every child might break. This is called the fragile base class problem, and it's the reason experienced developers are cautious about deep inheritance hierarchies.
Imagine you have this hierarchy:
1Animal
2 └── Pet
3 └── Dog
4 └── ServiceDog
5 └── GuideDogFive levels deep. Now suppose you need to change how Pet.eat() works. That change cascades through Dog, ServiceDog, and GuideDog. Each might have overridden eat() with behavior that depends on the old implementation. Each might break in subtle ways that don't show up until runtime.
Steve McConnell in Code Complete recommended a maximum inheritance depth of 3 levels. The Gang of Four (Gamma, Helm, Johnson, Vlissides) in their Design Patterns book put it more bluntly: "Favor composition over inheritance." That line, published in 1994, changed the trajectory of software design.
Warning Signs Your Hierarchy Is Too Deep
- More than 3 levels of inheritance
- You're creating subclasses just to override one method
- Your class names look like
FlyingFireBreathingWizardWarrior - Changing a base class requires testing 15 subclasses
- You find yourself fighting the parent class instead of extending it If you see any of these, stop inheriting and start composing.
The Rectangle-Square Disaster
Here's the classic example that breaks every student's brain:
A square is a rectangle where width equals height. So naturally, Square extends Rectangle. Right?
1CLASS Rectangle
2 width: Integer
3 height: Integer
4
5 METHOD setWidth(w)
6 this.width = w
7 END METHOD
8
9 METHOD setHeight(h)
10 this.height = h
11 END METHOD
12
13 METHOD area()
14 RETURN this.width * this.height
15 END METHOD
16END CLASS
17
18CLASS Square EXTENDS Rectangle
19 METHOD setWidth(w)
20 this.width = w
21 this.height = w // Must keep them equal!
22 END METHOD
23
24 METHOD setHeight(h)
25 this.width = h // Must keep them equal!
26 this.height = h
27 END METHOD
28END CLASSNow imagine code that expects a Rectangle:
1FUNCTION doubleWidth(rect)
2 rect.setWidth(rect.width * 2)
3 ASSERT rect.area() == rect.width * rect.height
4END FUNCTIONPass a Rectangle: width doubles, height stays. Area equals width times height. Works.
Pass a Square: width doubles, but setWidth also sets height. Now height changed when only width should have. The assertion fails. The code is wrong, even though a square IS mathematically a rectangle.
This is the Liskov Substitution Principle in action (which we'll cover formally in Lesson 7): a subclass must be substitutable for its parent without breaking behavior. Square violates this, and it's the reason experienced developers never use this inheritance without pausing.
Composition Over Inheritance
If inheritance is a family tree, composition is Lego blocks. Instead of building a rigid hierarchy, you assemble objects from interchangeable parts.
HAS-A Relationships
Instead of Dog extends SwimmingAnimal, try Dog has a SwimmingBehavior:
1CLASS SwimmingBehavior
2 METHOD swim()
3 PRINT "Swimming through water"
4 END METHOD
5END CLASS
6
7CLASS FlyingBehavior
8 METHOD fly()
9 PRINT "Soaring through the air"
10 END METHOD
11END CLASS
12
13CLASS Dog
14 name: String
15 swimming: SwimmingBehavior = new SwimmingBehavior()
16
17 METHOD swim()
18 this.swimming.swim()
19 END METHOD
20END CLASS
21
22CLASS Duck
23 name: String
24 swimming: SwimmingBehavior = new SwimmingBehavior()
25 flying: FlyingBehavior = new FlyingBehavior()
26
27 METHOD swim()
28 this.swimming.swim()
29 END METHOD
30
31 METHOD fly()
32 this.flying.fly()
33 END METHOD
34END CLASSA Dog can swim. A Duck can swim AND fly. We didn't need a SwimmingAnimal and a FlyingSwimmingAnimal hierarchy. We just composed behaviors. Want a FlyingFish? Give it SwimmingBehavior and FlyingBehavior. No new classes in the hierarchy needed.
The Game Character Example
Imagine building characters for a role-playing game with inheritance:
1Character
2 └── Warrior
3 └── Mage
4 └── Archer
5 └── HealingWarrior (extends Warrior? extends Healer?)
6 └── FireMageArcher (extends... um...)This falls apart immediately. What about a character that's both a warrior and a healer? Multiple inheritance? Most languages don't allow it.
With composition:
1CLASS Character
2 name: String
3 abilities: List<Ability> = []
4
5 METHOD addAbility(ability)
6 this.abilities.add(ability)
7 END METHOD
8
9 METHOD useAbility(abilityName)
10 FOR EACH ability IN this.abilities
11 IF ability.name == abilityName THEN
12 ability.execute(this)
13 END IF
14 END FOR
15 END METHOD
16END CLASS
17
18// Create any combination
19hero = new Character("Gandalf")
20hero.addAbility(new MeleeAttack())
21hero.addAbility(new FireMagic())
22hero.addAbility(new Healing())
23// Gandalf is now a melee-fighting fire mage who can healThe Diamond Problem
Here's the scenario that made language designers lose sleep: if B extends A and C extends A, what happens when D extends both B and C?
1 A
2 / \
3 B C
4 \ /
5 DIf A has a method greet(), and both B and C override it differently, which version does D get?
Different languages handle this differently:
C++: Allows it. Uses "virtual inheritance" to resolve ambiguity. Complex and error-prone.
Java and C#: Forbid it entirely. You can only extend one class. Use interfaces (Lesson 6) for multiple contracts.
Python: Allows it. Uses Method Resolution Order (MRO), a linearization algorithm that defines a predictable lookup sequence. Still confusing.
Go and Rust: Skip class inheritance entirely. Use composition and interfaces (traits) instead.
The trend is clear: modern languages are moving away from inheritance and toward composition and interfaces. The diamond problem is one of the main reasons why.
When to Inherit vs When to Compose
Use inheritance when:
- There is a genuine, permanent IS-A relationship
- The hierarchy is shallow (3 levels max)
- Subclasses genuinely specialize the parent without breaking its contract
Use composition when:
- You need multiple behaviors (can't multiply inherit)
- The relationship is HAS-A, not IS-A
- You need flexibility to swap behaviors at runtime
- You're unsure (when in doubt, compose)
Default to composition. Upgrade to inheritance only when it clearly, unambiguously fits.
"Inheritance is not a tool for code reuse. It's a tool for expressing genuine type relationships. If you're inheriting just to avoid retyping methods, you're building a house of cards." This principle took the industry two decades to fully internalize.