Last Updated: February 12, 2026
Inheritance allows one class (called the subclass or child class) to inherit the properties and behaviors of another class (called the superclass or parent class).
In simpler terms:
Inheritance enables code reuse by letting you define common logic once in a base class and then extend or specialize it in multiple derived classes.
This leads to cleaner, modular, and more maintainable software.
Think of a User system in a web application:
User class holds common attributes like username, email, and methods like login() or logout().Admin, Customer, and Vendor inherit from User but add role-specific behavior.All specialized user types inherit common data and behaviors from the User class, but can extend functionality to suit their roles.
Inheritance offers several benefits that make it a powerful design tool in OOP.
It embodies the DRY (Don't Repeat Yourself) principle. Common logic is written once in the parent class and shared across all subclasses reducing redundancy.
It creates a clear and intuitive hierarchy that model real-world “is-a” relationships like ElectricCar is a Car or Admin is a User.
If a bug is found or a change is needed in the shared logic, you only need to fix it in one place, the superclass. All subclasses automatically inherit the fix.
Inheritance is a prerequisite for polymorphism, allowing objects of different subclasses to be treated as objects of the superclass.
When a class inherits from another:
This allows for both reuse and customization.
The most basic form of inheritance is a child class that extends a parent class and adds new behavior on top of the inherited fields and methods. Here's the vehicle hierarchy built with inheritance.
This Vehicle class defines basic attributes and common behaviors shared by all cars.
Now you can create specialized types of vehicles:
In this example:
ElectricCar and GasCar inherit the make, model, startEngine(), and stopEngine() methods from the Vehicle class.Not all inheritance hierarchies look the same. There are several common patterns, each with its own structure and trade-offs.
Single Inheritance is the simplest form: one child class extends one parent class. The ElectricCar extends Vehicle relationship is single inheritance. This is the most common pattern and the one supported by all major languages.
Multi-level Inheritance is when a child class itself becomes a parent. For example, Vehicle -> Car -> ElectricCar. Each level adds more specialization. This is fine in moderation, but deep chains (5+ levels) become fragile and hard to understand.
Hierarchical Inheritance is when multiple child classes extend the same parent. Our vehicle example, where both ElectricCar and GasCar extend Vehicle, is hierarchical inheritance. This is extremely common and perfectly natural.
Multiple Inheritance is when a child class extends more than one parent. This is where things get complicated. Only C++ and Python support multiple inheritance directly. Java, C#, and TypeScript do not. The reason? The diamond problem.
Imagine ElectricCar extends both Vehicle and Machine. Both Vehicle and Machine have a start() method. When you call electricCar.start(), which version runs? The one from Vehicle? The one from Machine? Both?
C++ handles this with virtual inheritance, which is complex and error-prone. Python handles it with the Method Resolution Order (MRO), a well-defined algorithm (C3 linearization) that determines which parent's method takes priority. Java and C# sidestep the problem entirely by only allowing single class inheritance, you can implement multiple interfaces, but extend only one class.
Inheritance is powerful, but it should be used intentionally, only when it truly models a real-world relationship. Getting this decision wrong early in your design leads to code that's hard to change, hard to test, and hard to reason about.
Here's a practical checklist.
Dog is an Animal, Car is a Vehicle). If you can't say "X is a Y" naturally, inheritance is probably the wrong tool. These relationships belong in composition.startEngine() method, so putting it in the parent avoids duplicating it across every vehicle type.Vehicle reference pointing to an ElectricCar, every Vehicle method should still work as expected.Car has an Engine, it is not an Engine. A Printer uses a Logger, it is not a Logger.FileLogger for a ConsoleLogger). With inheritance, the parent relationship is fixed.When in doubt, start with composition. You can always refactor toward inheritance later if a genuine "is-a" hierarchy emerges. Going the other direction, untangling a deep inheritance tree into composition, is much harder.
Let's apply inheritance to a completely different domain to show that these patterns aren't limited to vehicles. Imagine you're building a notification system that can send messages through different channels: email, SMS, and push notifications.
All notification types share common properties: a recipient, a message, and a timestamp. They all need a formatHeader() method that produces a consistent header format. But the send() method works differently for each channel, email needs a subject line, SMS has a character limit, and push notifications have a device token and priority level.
recipient, message, and timestamp fields are defined in Notification. The formatHeader() method is inherited by all three notification types, producing a consistent header format across email, SMS, and push. If you want to change the timestamp format, you change one method.SMSNotification handles the 160-character limit. PushNotification manages device tokens and priority. EmailNotification adds a subject line. None of these details leak into the parent or into each other.SlackNotification extends Notification, add a webhookUrl field, override send(). No existing code changes.