Loading content...
Every experienced software engineer has faced this moment: you're designing a new class, and you realize it shares significant functionality with a class you've already written. Perhaps you're building a SavingsAccount class and notice it needs most of the same attributes and methods as your existing BankAccount class. Or you're creating a ElectricCar class and recognize it should behave like your Car class with some additional features.
The naive solution—copying and pasting code—creates a maintenance nightmare. Change the original, and you must remember to update every copy. Miss one, and you've introduced subtle bugs that may not surface for months.
Inheritance is object-oriented programming's elegant solution to this fundamental problem. It allows one class to acquire the properties and behaviors of another, establishing a hierarchical relationship that promotes code reuse while maintaining a single source of truth.
By the end of this page, you will understand exactly what inheritance is, why it exists, and how it fundamentally changes the way we think about organizing code. You'll grasp the precise mechanics of how one class 'inherits' from another and understand the foundational vocabulary that every object-oriented developer must command.
Inheritance is a mechanism in object-oriented programming whereby a new class (called the derived class, subclass, or child class) is created from an existing class (called the base class, superclass, or parent class) by incorporating all of the parent's attributes and methods, with the ability to add new ones or modify inherited behaviors.
Let's dissect this definition precisely:
At its core, inheritance answers this question: 'How can I create a new type that is like an existing type, but different in specific ways?' The answer is to inherit what stays the same and override or extend what differs.
The mathematical perspective:
From a type theory standpoint, inheritance establishes a subtype relationship. If class B inherits from class A, then every instance of B is also an instance of A. In set-theoretic terms, the set of all B objects is a subset of the set of all A objects.
This has profound implications:
A is expected, a B can be providedA is valid on a BB has at least the capabilities of an A, possibly moreThis is the foundation of polymorphism, which we'll explore in detail in later chapters. For now, understand that inheritance creates a type hierarchy where child types are compatible with parent types.
To truly understand inheritance, we must understand the problems that existed before it—the problems that drove language designers to create this mechanism.
The Copy-Paste Anti-Pattern:
Before inheritance, if you wanted two classes to share behavior, you had limited options:
Each approach has significant drawbacks:
| Approach | Implementation | Drawbacks |
|---|---|---|
| Copy-Paste | Duplicate code in each class | Maintenance nightmare; bugs must be fixed in multiple places; no guarantee of consistency |
| Utility Functions | Standalone functions that operate on data | Breaks encapsulation; data and behavior separated; procedural rather than object-oriented |
| Composition Only | Classes contain helper objects | Doesn't establish 'is-a' relationships; can't substitute one type for another; verbose delegation |
A concrete example of the problem:
Imagine you're building a system that models different types of employees. You have:
Employee — with attributes like name, id, salary and methods like calculatePay(), getDetails()Manager — an employee who also has directReports and methods like assignTask(), conductReview()Engineer — an employee who also has programmingLanguages and methods like writeCode(), reviewCode()Without inheritance, you'd have to:
Employee attributes and methods into both Manager and Engineer, ORManager and Engineer each contain an Employee object and delegate every callBoth approaches are unsatisfying. The first creates code duplication. The second doesn't let you treat managers and engineers as employees—you can't put them in an Employee[] array or pass them to functions expecting an Employee.
Inheritance solves two problems simultaneously: (1) code reuse without duplication, and (2) type compatibility between related classes. You get both the shared implementation AND the ability to treat derived types as their base type. This dual benefit is why inheritance became a pillar of OOP.
Understanding where inheritance came from illuminates why it works the way it does.
Simula 67: The Birthplace of Inheritance
Inheritance as a programming concept was first introduced in Simula 67, developed by Ole-Johan Dahl and Kristen Nygaard at the Norwegian Computing Center. Simula was designed for simulation modeling—representing real-world entities and their behaviors in code.
The designers noticed that simulations often involve entities that share characteristics. A simulation of a harbor might have Ship, CargoShip, and PassengerShip. All ships share certain properties (size, speed, position), but specialized ships have additional attributes and behaviors.
Rather than forcing programmers to duplicate the common 'ship' logic, Dahl and Nygaard introduced the concept of a class hierarchy. A class could be declared as a subclass of another, automatically incorporating the parent's definition.
Smalltalk: Inheritance Matures
Alan Kay's Smalltalk (developed at Xerox PARC in the 1970s) refined inheritance into the form we recognize today. Smalltalk introduced:
Object)C++ and Java: Industry Adoption
Bjarne Stroustrup's C++ (1980s) brought inheritance to systems programming, while James Gosling's Java (1990s) made it mainstream for enterprise development. Both languages refined the syntax and semantics, introducing concepts like access modifiers for inheritance, abstract classes, and interface inheritance.
Inheritance was designed for modeling real-world hierarchies—simulation domains where 'kinds of things' naturally existed. When you use inheritance, you're most effective when your domain actually has these hierarchical IS-A relationships. Using inheritance for pure code reuse (without genuine type relationships) often leads to design problems.
Let's examine precisely what happens when one class inherits from another. Understanding the mechanics helps you reason about behavior and avoid common pitfalls.
What the child class receives:
Memory layout perspective:
When you create an instance of a child class, the object in memory contains:
The child object is literally a 'superset' of the parent object. This is why a child can be used wherever a parent is expected—it has everything the parent has, plus potentially more.
Method resolution:
When you call a method on a child object:
This mechanism—called method dispatch or virtual method table lookup—is what enables polymorphism. The same method call can execute different code depending on the actual type of the object.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172
// Parent class (base class, superclass)class Animal { // Public attribute - inherited and accessible public name: string; // Protected attribute - inherited, accessible only in hierarchy protected age: number; // Private attribute - NOT accessible in child (but exists in memory) private internalId: string; constructor(name: string, age: number) { this.name = name; this.age = age; this.internalId = `animal_${Date.now()}`; } // Public method - inherited and callable public speak(): string { return `${this.name} makes a sound`; } // Protected method - available to children protected getAgeInMonths(): number { return this.age * 12; } // Private method - NOT accessible in child private generateId(): string { return `id_${Math.random()}`; }} // Child class (derived class, subclass)class Dog extends Animal { // Child adds its own attribute public breed: string; constructor(name: string, age: number, breed: string) { // Must call parent constructor first super(name, age); this.breed = breed; } // Child overrides parent method public speak(): string { return `${this.name} barks!`; // ✅ Can access inherited 'name' } // Child adds its own method public fetch(): string { const months = this.getAgeInMonths(); // ✅ Can access protected method return `${this.name} (${months} months old) fetches the ball!`; } // This would NOT compile: // private showId(): string { // return this.internalId; // ❌ Cannot access private parent member // }} // Usage demonstrating inheritance mechanicsconst myDog = new Dog("Max", 3, "Golden Retriever"); console.log(myDog.name); // ✅ "Max" - public inheritedconsole.log(myDog.breed); // ✅ "Golden Retriever" - child's ownconsole.log(myDog.speak()); // ✅ "Max barks!" - overridden methodconsole.log(myDog.fetch()); // ✅ Calls child method using protected parent method // Type compatibility - Dog IS-A Animalconst animal: Animal = myDog; // ✅ Valid - child can be assigned to parent typeconsole.log(animal.speak()); // ✅ "Max barks!" - polymorphism in actionObject-oriented programming uses multiple terms for the same concepts. Fluency requires knowing all variants and their nuances.
| Concept | Terms Used | Context Notes |
|---|---|---|
| The class being extended | Parent class, Base class, Superclass | "Parent" emphasizes hierarchy; "Base" emphasizes foundation; "Super" is Java/Kotlin convention |
| The class that extends | Child class, Derived class, Subclass | "Child" emphasizes hierarchy; "Derived" emphasizes creation from another; "Sub" is Java convention |
| The act of extending | Inherits from, Extends, Derives from | "Extends" is the Java/TypeScript keyword; "Derives" is common in C++/C# discussion |
| The relationship type | IS-A relationship, Subtype relationship | "IS-A" is design terminology; "Subtype" is type theory terminology |
| Creating a child object | Instantiation of a derived type | The child is still instantiated normally; inheritance affects what's in the instance |
| Changing inherited behavior | Overriding, Redefining | "Override" is standard; some older texts use "redefine" |
| Calling parent's version | super.method(), base.method() | "super" in Java/TypeScript/Python; "base" in C# |
In this curriculum, we'll use 'parent/child' for intuitive discussion and 'superclass/subclass' when being technical. Both are correct—use what your team uses, but understand all variants.
Important distinctions:
Inheritance vs. Implementation:
Inheritance vs. Subtyping:
extends vs. implements:
extends creates an inheritance relationship with a classimplements creates a contractual relationship with an interfaceNot all inheritance is the same. Understanding the different forms helps you recognize what your language supports and what patterns are possible.
Dog extends Animal — Dog has exactly one parent.FlyingFish extends Fish, Bird — FlyingFish inherits from two parents. This introduces the 'diamond problem' we'll discuss in later modules.Animal → Mammal → Dog — Dog inherits from Mammal, which inherits from Animal.Animal is extended by both Dog and Cat. This creates a fan-out structure.Visual representation of inheritance types:
Multiple inheritance (where a class has more than one parent class) introduces significant complexity, including the 'diamond problem' where the same method might be inherited through multiple paths. Most modern languages (Java, C#, TypeScript) intentionally avoid multiple inheritance of classes, allowing only multiple inheritance of interfaces.
Inheritance is a powerful tool, but it's not always the right tool. Understanding when inheritance genuinely applies—and when it's being misused—is crucial for good design.
The litmus test: IS-A relationship
Inheritance should model a genuine IS-A relationship:
Dog IS-A Animal ✅SavingsAccount IS-A BankAccount ✅Circle IS-A Shape ✅If you can't naturally say "X is a Y" where Y is the parent and X is the child, inheritance may be inappropriate:
Car IS-A Engine? ❌ (A car HAS-A engine)Stack IS-A ArrayList? ❌ (A stack uses an ArrayList internally, HAS-A)Logger IS-A FileWriter? ❌ (A logger may use a FileWriter, HAS-A)Use inheritance for TYPE RELATIONSHIPS, not for code reuse. If you're inheriting just to get some useful methods, consider composition instead. If X truly IS-A Y in your domain, inheritance is appropriate. If X merely USES functionality similar to Y, prefer composition.
Inheritance is one of several mechanisms for code reuse and abstraction. Understanding how it compares helps you choose the right tool.
| Mechanism | What It Provides | Relationship Type | When to Use |
|---|---|---|---|
| Inheritance | Type hierarchy + shared implementation | IS-A (subtype) | When child truly is a specialized version of parent |
| Composition | Objects containing other objects | HAS-A (containment) | When object uses another object's functionality |
| Aggregation | Objects referencing other objects (weak ownership) | HAS-A (association) | When object logically contains but doesn't control lifetime |
| Interfaces | Contract without implementation | CAN-DO (capability) | When defining capabilities without dictating implementation |
| Mixins/Traits | Reusable behavior units | ACTS-LIKE (behavior inclusion) | When multiple unrelated classes need same behavior |
The famous principle: Favor Composition Over Inheritance
This principle, popularized by the Gang of Four in Design Patterns, recognizes that inheritance creates tight coupling. When you inherit:
Composition, by contrast:
This doesn't mean inheritance is bad—it means it should be used deliberately, for genuine type hierarchies, not as a default code-sharing mechanism.
We've established a comprehensive understanding of what inheritance is. Let's consolidate the key points:
What's next:
Now that we understand what inheritance is in abstract terms, the next page explores the parent/child relationship in depth. We'll examine how superclasses and subclasses interact, the mechanics of constructor chaining, and the semantics of the 'extends' relationship that binds parent and child classes together.
You now have a precise, comprehensive definition of inheritance. You understand its purpose, mechanics, historical context, and appropriate use cases. This foundation will support everything else you learn about inheritance and object-oriented hierarchies.