Site Logo - Homepage

My Attempt At Explaining: Inheritance(Object-Oriented Programming)

Published on: January 31, 2026
Illustration showing an inheritance diagram

The roots of the word inheritance

The word inheritance comes from the Old French enheriter meaning “to make someone an heir”. It fundamentally referred to the act of receiving possessions or titles from an ancestor or predecessor.
However, over time the concept broadened. By the 19th century, the term began to be used metaphorically in science and biology to describe the passing of traits. Gregor Mendel pioneered this development, by demonstrating that traits are determined by discrete “units” of inheritance, what we now call genes. The biological term heredity, from the Latin hereditatem, emerged in parallel, but inheritance became the more widely used term to refer specifically to genetic transmission of traits from parent to offspring. In simple words inheritance is anything you receive from those before you, whether it’s a house, a name, or your mother’s eyes.

Inheritance in Object-Oriented Programming

Inheritance in programming draws from the same idea of passing down traits. This is implemented either through objects (prototype-based inheritance: Javascript) or classes (class-based inheritance: Java, Python, C++). Here we will focus on class based inheritance.

In most object-oriented languages like Java, a new class called a subclass or child class inherits the properties and behaviors (methods and attributes) of an existing class called the superclass, or parent class.

Let’s illustrate this concept with the Java code below:

    // Parent class (superclass)
    class Vehicle {

        // attributes
        public String make;
        public String model;
        public int year;

        public Vehicle(String make, String model, int year) {
            this.make = make;
            this.model = model;
            this.year = year;
        }

        public void displayInfo() {
            System.out.println("Vehicle: " + year + " " + make + " " + model);
        }

        public void start() {
            System.out.println("The vehicle is starting...");
        }
    }

    // Child class (subclass)
    class Car extends Vehicle {
        // property specific to Car
        private int numberOfDoors;

        public Car(String make, String model, int year, int numberOfDoors) {
            // Parent constructor called using super
            super(make, model, year);
            this.numberOfDoors = numberOfDoors;
        }

        // Parent method overridden
        @Override
        public void displayInfo() {
            System.out.println("Car: " + year + " " + make + " " + model +
                    ", " + numberOfDoors + " doors");
        }

        // Method specific to Car
        public void honk() {
            System.out.println("Beep beep!");
        }
    }

    public class Demo {
        public static void main(String[] args) {
            // Creates a Vehicle object
            Vehicle vehicle = new Vehicle("General Motors", "Cadillac Escalade", 2025);
            vehicle.displayInfo();
            vehicle.start();

            System.out.println("\n-----------------\n");

            // Creates a Car object
            Car car = new Car("Toyota", "Camry", 2023, 4);
            car.displayInfo();  // Overridden method
            car.start();        // Inherited method
            car.honk();         // Specific method to Car
        }
    }

Output:

    Vehicle: 2025 General Motors Cadillac Escalade
    The vehicle is starting...

    -----------------

    Car: 2023 Toyota Camry, 4 doors
    The vehicle is starting...
    Beep beep!

In the example above, we have two classes Vehicle and Car.

The Vehicle class is the superclass (or parent class). It defines the common attributes that many types of vehicles might share: make, model, and year as well as shared behaviors with methods like displayInfo() which prints the vehicle’s details and start(), which simulates the starting of the vehicle.

The Car class is the subclass (or child class), meaning that Car inherits all non-private members (attributes and methods) of Vehicle. As a result, a Car object automatically possesses the make, model, and year attributes and can invoke the start() method even though they are not explicitly defined within the Car class. This is demonstrated in the main() method when car.start() is called, it uses the start() method from Vehicle.

Inside the Car constructor, you will notice the line super(make, model, year);. This is a standard and often necessary step when defining a subclass constructor, When a Car object is instantiated, theVehicle part of the object must also be initialized. The super() call invokes the constructor of the Vehicle superclass, passing along the necessary arguments (make, model, year) to set up those inherited attributes.

Another important thing to point out is that a subclass is not just a duplicate of its superclass; it can also have more specific characteristics. In this case the Car class adds its own attribute, private int numberOfDoors;, which is specific to cars (or at least, more specific than just any “vehicle”). It also adds a new method unique to Car: public void honk(). A general Vehicle might not honk, but a Car typically does. Thus, inheritance allows us to extend the functionalities of a superclass.

Furthermore, the Car class overrides the displayInfo() method. This is known as method overriding, where a subclass provides its own version of a method it inherits. When car.displayInfo() is executed in the main method, the overridden version in the Car class runs, printing the number of doors in addition to the general vehicle info.

The output reflects all of these principles:

  • The Vehicle object, behaves as defined in the Vehicle class.

  • The Car object uses the overridden displayInfo() method from Car.

  • It inherits the start() method from Vehicle.

  • And it calls the honk() method defined in Car.

Just as inheritance in biology passes traits from parents to children, our Vehicle class passed its traits to Car.

In Essence, Inheritance Allows You To:

  • Reuse common code such as make, model, year, and shared behaviors like start(), to avoid redundancy.
  • Extend the functionality of a superclass by adding subclass-specific attributes (numberOfDoors) and methods (honk()).
  • Customize or specialize inherited behavior through method overriding, as seen in displayInfo().

Types of inheritance

There are different types of inheritance in object-oriented programming.

  1. Single Inheritance: A subclass inherits properties and behaviors from only one direct superclass. This is the simplest and most widely used form of inheritance. It is like a child inheriting traits directly from one parent. This type of inheritance is supported by almost all object-oriented programming languages (Java, C#, Python, etc.)
Illustration showing a single inheritance diagram
  1. Multiple Inheritance: A subclass inherits properties and behaviours from more than one superclass. This means the subclass can combine features such as attributes and methods from multiple parent classes simultaneously.
    While powerful, multiple inheritance introduce complexities like the “Diamond Problem.” Let’s say Superclass A and Superclass C both inherit from a common grandparent (say, Superclass D) and Subclass B inherits from both A and C. If A and C have both overridden a method from D differently, an ambiguity arise, which version does B inherit?
    Languages that support multiple inheritance(C++, Python, Perl…) provide different mechanisms to resolve complexities like this. For example C++ uses virtual inheritance to resolve this. When A and C virtually inherit from D, and B inherits from A and C, it tells the compiler to create only one shared copy of D. This shared copy prevents the duplication of D’s members within B. B can now access D’s features through this single shared object. Multiple class inheritance is not directly supported by languages like Java and C#; they use instead interfaces to achieve the same thing.
Illustration showing a multiple inheritance diagram
  1. Multilevel Inheritance: A subclass inherits from a superclass, which itself is a subclass of another superclass. This creates a chain of inheritance, a grandchild inherits from a parent, who in turn inherited from a grandparent. This type of inheritance is supported by most OOP languages (Java, Python, C++, etc…).
Illustration showing a multilevel inheritance diagram
  1. Hierarchical Inheritance: Multiple subclasses inherit from a single superclass. This means one parent class serves as the base class for several child classes for example one parent having multiple distinct children, each inheriting common traits but also having their own unique ones. It is supported as well by most OOP languages (Java, Python, C++…).
Illustration showing a hierarchical inheritance diagram
  1. Hybrid Inheritance: A combination of two or more of the above types of inheritance. For instance, a class inheriting from multiple classes, some through multilevel inheritance and others through multiple inheritance. Hybrid inheritance naturally arises in languages that offer a robust support for various inheritance models, such as C++ or Python, but it should be designed and used with care.
Illustration showing a hybrid inheritance diagram

When to use Inheritance

Inheritance is a powerful tool, but as with any programming concept, it should be applied thoughtfully. When misused, inheritance can create an overly complex class hierarchy that becomes difficult to maintain and extend which sometimes lead to the infamous “fragile base class problem” where changes to a parent class unexpectedly break the child classes.

This why sometimes instead of a class being something (inheritance), it’s better for a class to have something (composition). Composition is a design principle where a class contains objects of other classes as instance variables, rather than inheriting from them. It represents a “has-a” relationship instead of an “is-a” relationship.

For example, a Car class has an Engine. The Car class would define an instance variable of type Engine. It wouldn’t make sense for Car to extend Engine because a car is not an engine; it has one. This “has-a” approach often leads to more flexible and less coupled designs.

class Engine {
    public void start() {
        System.out.println("Engine started.");
    }
}


class Car {
    private Engine engine; // Car has an Engine

    // Car is given an Engine when it is created
    public Car(Engine engine) {
        this.engine = engine;
    }

    public void startCar() {
        System.out.println("Car is starting...");
        engine.start(); // Car delegates to its Engine
        System.out.println("Car is running.");
    }
}

public class Demo {
    public static void main(String[] args) {
        Engine myEngine = new Engine();
        Car myCar = new Car(myEngine); // Car is composed with an Engine

        myCar.startCar();
    }
}

The output will be:

Car is starting...
Engine started.
Car is running.

So, when should you lean towards inheritance? Consider using it when:

  • You have a clear “is-a” relationship between classes(a Car is a Vehicle, a Dog is an Animal).
  • Multiple classes share common behaviours and attributes that can be abstracted into a superclass.
  • The relationship between the classes is relatively stable and unlikely to change frequently.
  • You want to avoid duplicating common code across related classes.
  • You want to treat objects of different subclasses uniformly by interacting with them through their common superclass type(polymorphism).

Conclusion

Just as we inherit traits from our ancestors, classes inherit properties and behaviour from their parent classes. Inheritance allows us to customize the inherited behavior and extend its functionality. From single inheritance to hybrid, each type of inheritance defines a structure that allows us to serve the different purpose of an application needs.

However, perhaps the most important lesson is knowing when not to use inheritance. The principle of “composition over inheritance” serves as a reminder that sometimes, being flexible is more important than following a strict hierarchy. A well-designed system should combine both approaches, using inheritance when there is a genuine “is-a” relationship and composition when there is a need for “has-a” relationships.
At the end of the day inheritance is a tool, not a goal, we should let the structure of the problem we are trying to solve guide the design choices.