Tuple Logo
inheritance-in-oop

SHARE

Inheritance

What is Inheritance?

Inheritance is a fundamental concept in object-oriented programming (OOP) that allows a class (the subclass) to inherit properties and behaviors from another class (the superclass). This reduces the need to duplicate code, as common functionality can be reused and extended in different contexts.

It enables developers to build class hierarchies, where general functionality is defined in base classes and more specific behavior is implemented in derived classes. For example, consider a general Animal class with methods like breathe() and eat(). A subclass such as Dog can inherit these methods and add specific behavior like bark().

Example:

class Animal:
    def breathe(self):
        print("Breathes")

    def eat(self):
        print("Eats food")

class Dog(Animal):
    def bark(self):
        print("Barks")

rex = Dog()
rex.breathe()  # Output: Breathes
rex.bark()     # Output: Barks

In the example above, the Dog class inherits methods from the Animal class and adds its own functionality. This is the core idea of inheritance: reuse existing functionality and extend it when needed.

Why inheritance is important

Inheritance plays a key role in organizing code effectively in object-oriented programming. It helps you build software that is maintainable, scalable, and reusable. Below are the main reasons why inheritance is valuable in practice.

Reduces code duplication

When multiple classes need similar properties or methods, you can define them once in a superclass. Subclasses inherit this functionality without needing to rewrite it. This avoids duplicated code and reduces the risk of errors.

Simplifies maintenance

Since shared functionality is centralized in the superclass, any changes only need to be made in one place. If you update a method in the superclass, all subclasses automatically benefit. This keeps maintenance efficient and consistent.

Improves extensibility

Inheritance makes it easy to expand on existing classes without modifying their internal logic. You can create new subclasses that behave differently or add extra features, while leaving the base class untouched.

Supports polymorphism

Polymorphism allows you to treat objects of different subclasses as objects of the same superclass. This means you can write generic functions that work with multiple object types, as long as they share the same interface. It leads to more flexible and scalable code.

Types of inheritance

There are several types of inheritance in object-oriented programming, depending on how classes are structured. The choice depends on the complexity of your project and how reusable your code needs to be. Below are the most common types.

Single inheritance

In single inheritance, a subclass inherits from one superclass. It’s the most straightforward and widely used form.

class Human:
    def speak(self):
        print("Speaking")

class Student(Human):
    def study(self):
        print("Studying")

s = Student()
s.speak()     # Output: Speaking

Multiple inheritance

In this case, a subclass inherits from more than one superclass. Not all programming languages support this because it can introduce ambiguity (such as the “diamond problem”).

class Vehicle:
    def move(self):
        print("Moving")

class Electric:
    def charge(self):
        print("Charging")

class E_Scooter(Vehicle, Electric):
    pass

Multilevel inheritance

This involves a chain of inheritance, where a class inherits from a subclass that itself inherits from another superclass.

class Animal:
    pass

class Mammal(Animal):
    pass

class Cat(Mammal):
    pass

Hierarchical inheritance

In hierarchical inheritance, multiple subclasses inherit from the same superclass. This creates a tree-like structure.

class Device:
    pass

class Laptop(Device):
    pass

class Phone(Device):
    pass

Hybrid inheritance

Hybrid inheritance combines two or more of the types above. It can lead to complexity and ambiguity, especially in languages that support multiple inheritance.

For example, combining multiple and multilevel inheritance within a single structure.

Subclasses and superclasses

In inheritance, subclasses and superclasses form the foundation of the class hierarchy. A superclass (also called a base class or parent class) contains shared properties and methods. A subclass (or child class) extends or customizes that functionality.

What exactly happens during inheritance?

When a subclass is defined, it inherits all public and protected members from the superclass. The subclass can:

Constructor chaining

When a subclass is instantiated, the constructor of the superclass is called first—unless explicitly overridden. In Python, this is done with super().__init__(), and in Java, you use super().

class Person:
    def __init__(self, name):
        self.name = name

class Employee(Person):
    def __init__(self, name, role):
        super().__init__(name)
        self.role = role

Now let’s look at a few special cases:

Non-subclassable classes

Some classes are not allowed to be inherited. These are marked with keywords like final (in Java, Swift, etc.). This is useful when you want to prevent a class from being extended or modified.

Non-overridable methods

Like classes, certain methods can be marked as final or sealed to prevent them from being overridden in a subclass. This adds a layer of safety and ensures predictable behavior.

Virtual methods

A virtual method is one that is intended to be overridden by subclasses. In languages like C#, the virtual keyword is used in the base class, and override is used in the subclass.

Access modifiers and visibility

In inheritance, the visibility of class members plays a crucial role. Not every property or method from the superclass is automatically accessible from a subclass. Programming languages use access modifiers to control this visibility. These modifiers determine who can access which parts of a class.

The three most common access levels are:

Public

A public member is accessible from anywhere — within the class, in its subclasses, and from outside. This is the most permissive level of access.

class Person:
    def __init__(self):
        self.name = "John"  # public by default in Python

Protected

Protected members are accessible within the class and its subclasses, but not from outside. In Python, these are typically marked with a single underscore (e.g. _role). In languages like Java or C#, the protected keyword is used.

class Person:
    def __init__(self):
        self._role = "Developer"

Private

Private members are only accessible within the class itself. In Python, these are defined with a double underscore (e.g. __salary). Other languages use the private keyword.

class Person:
    def __init__(self):
        self.__salary = 5000  # Not directly accessible in subclass

Access modifiers help protect internal logic, prevent misuse of data, and support encapsulation — another key principle of object-oriented programming.

Overriding and polymorphism

Inheritance allows you to reuse methods from a superclass, but sometimes you want to change how a method behaves. That’s where overriding comes in. Combined with polymorphism, it makes object-oriented systems powerful and flexible.

What is overriding?

Method overriding means redefining a method in a subclass that already exists in the superclass. The new version in the subclass replaces the original one.

class Animal:
    def sound(self):
        print("Makes a sound")

class Cat(Animal):
    def sound(self):
        print("Meow")

pet = Cat()
pet.sound()  # Output: Meow

In this example, the Cat class overrides the sound() method. When called, the subclass version is used instead of the one from Animal.

Difference from overloading

Overriding is not the same as overloading. Overloading means having multiple methods with the same name but different parameters. It’s more common in statically typed languages like Java and C#, but not in Python.

What is polymorphism?

Polymorphism means that objects of different subclasses can be treated the same way through a shared interface. You interact with them as if they’re instances of the superclass, while their actual behavior comes from the subclass.

def make_sound(animal: Animal):
    animal.sound()

make_sound(Cat())   # Output: Meow
make_sound(Dog())   # Output: Bark

As long as an object has the required method, polymorphism lets you use it without knowing its exact type. This supports flexible, generic programming.

Applications of inheritance

Inheritance is not a goal in itself but a tool to make your code cleaner, smarter, and easier to extend. In practice, it’s often used to create structure and encourage reuse in larger codebases.

Code reuse and the DRY principle

One of the most common applications is code reuse. By placing shared functionality in a superclass, you don’t have to repeat it in every subclass. This aligns with the DRY principle (Don't Repeat Yourself), which aims to reduce redundancy in code.

Example: Imagine you have different types of users in a system — Admin, Editor, Guest. Instead of giving each class its own login() and logout() methods, you define them once in a shared User superclass.

Separating structure from logic

Inheritance helps you separate general logic from specific behavior. You can create a general base class that handles core functionality, and then write subclasses that focus on specific roles, devices, or behaviors.

For example:

class Message:
    def send(self):
        print("Sending...")

class Email(Message):
    def send(self):
        print("Email sent")

class SMS(Message):
    def send(self):
        print("SMS sent")

You can now send any kind of message using the same method, regardless of its type.

Enabling polymorphic behavior

As mentioned earlier, inheritance enables polymorphism. This is especially useful when writing general-purpose functions that can handle multiple types of objects — like documents, products, or users — as long as they all inherit from the same base class.

Inheritance vs subtyping

Inheritance and subtyping are closely related concepts in object-oriented programming, but they are not the same. While they often appear together, understanding the distinction is important for designing clean and maintainable code.

What is subtyping?

Subtyping means that an object of a subclass can be used anywhere an object of its superclass is expected. This aligns with the Liskov Substitution Principle (LSP), which states that a subclass should be substitutable for its superclass without altering the expected behavior.

class Bird:
    def fly(self):
        pass

class Sparrow(Bird):
    def fly(self):
        print("The sparrow takes off")

def make_it_fly(bird: Bird):
    bird.fly()

In this example, Sparrow can be used anywhere Bird is expected — a valid case of subtyping.

How it differs from inheritance

Inheritance is the mechanism by which a class reuses code from another class.

Subtyping is the relationship where a subclass can safely stand in for a superclass.

Not all inheritance relationships qualify as good subtypes. If a subclass changes behavior in ways that violate expectations from the superclass, you may still have inheritance—but not valid subtyping.

Design constraints and pitfalls

Inheritance can be misused and lead to fragile designs if not applied carefully:

That’s why you’ll often hear the advice: "Favor composition over inheritance." Use inheritance only when there is a clear semantic connection, where the subclass truly is a kind of the superclass.

Challenges and alternatives

While inheritance is a powerful concept, it also has its downsides. In larger codebases, improper use of inheritance can lead to rigid, hard-to-maintain structures. That’s why it's important to understand its limitations — and when to consider alternatives.

Common issues

Tight coupling

Subclasses often rely heavily on the internal implementation of their superclass. Changes to the superclass can introduce unexpected bugs in all subclasses, reducing flexibility.

Rigid hierarchies

Once you build an inheritance hierarchy, it becomes difficult to refactor. Deep chains of inheritance increase complexity and make debugging harder.

Unnecessary inheritance

Sometimes, a subclass inherits methods it doesn’t actually need. This leads to bloated classes with unclear responsibilities.

Limited reusability

Inheritance only works within the same class hierarchy. If you want to reuse functionality across unrelated classes, inheritance becomes a limitation.

Alternatives to inheritance

Composition over inheritance

Instead of creating subclasses, you can compose objects. In composition, an object is made up of other objects that handle specific responsibilities. This leads to more modular and testable code.

class Engine:
    def start(self):
        print("Engine started")

class Car:
    def __init__(self):
        self.engine = Engine()

    def drive(self):
        self.engine.start()
        print("Car is driving")

Interfaces and abstract classes

Many languages support interfaces or abstract base classes. These define a structure or contract that subclasses must follow — without forcing them to inherit implementation. This avoids the downsides of code inheritance while keeping things consistent.

Mixins

In languages like Python, mixins are small classes that provide reusable functionality. They can be added to multiple classes without creating deep hierarchies.

Use inheritance wisely

Inheritance is a powerful concept in object-oriented programming that allows one class to reuse the properties and behavior of another. It helps keep code maintainable, supports polymorphism, and brings structure to larger systems.

However, inheritance is not always the best solution. If used incorrectly, it can lead to tightly coupled code and rigid structures that are hard to change. That’s why it’s important to use inheritance deliberately and sparingly. Consider alternatives like composition, interfaces, or mixins when they offer more flexibility or clarity.

In short: use inheritance when there’s a clear is-a relationship, and turn to other techniques when you need better separation of concerns or behavior reuse without hierarchy.

Frequently Asked Questions
What is inheritance with an example?

Inheritance is a principle in object-oriented programming where a subclass inherits properties and behaviors from another class, known as the superclass. This allows you to reuse and structure code more efficiently. For example, imagine a Animal class with a method breathe(). A Dog class that inherits from Animal can automatically use the breathe() method without needing to define it again.


What are the 4 types of inheritance?

The most common types of inheritance are single inheritance, multiple inheritance, multilevel inheritance, and hierarchical inheritance. Each defines a different structure for how classes inherit from one another. In some situations, combinations of these types are used, which is referred to as hybrid inheritance. The exact support for these types depends on the programming language you're using.


What is inheritance and polymorphism in programming?

Inheritance allows a subclass to access the attributes and methods of its superclass. Polymorphism enables different objects to be treated in the same way through a shared interface. This means you can write one function that works with multiple object types, as long as they share a certain method, resulting in flexible and scalable behavior in your application.


Articles you might enjoy

Piqued your interest?

We'd love to tell you more.

Contact us
Tuple Logo
Veenendaal (HQ)
De Smalle Zijde 3-05, 3903 LL Veenendaal
info@tuple.nl‭+31 318 24 01 64‬
Quick Links
Customer Stories