Inheritance in Python

Inheritance in Python is an object-oriented feature that lets one class reuse and extend the behavior of another class. The class that provides existing functionality is called the base class or parent class, and the class that derives from it is called the child class or derived class. This helps reduce duplication because common attributes and methods can stay in one place while specialized behavior is added in child classes.

In practice, inheritance is useful when several objects share a common structure but still need different behavior. For example, different kinds of accounts may all need a customer name and balance, while each account type applies its own rules for withdrawal, interest, or fees. Instead of rewriting the common code in every class, you place the shared logic in the parent and extend it in the child.

Syntax of Inheritance in Python

A child class is defined by placing the parent class name inside parentheses. Once the child class is created, it can access the parent methods and can also add its own methods.

class Animal:
    def eat(self):
        print("Animal is eating")

class Dog(Animal):
    def bark(self):
        print("Dog is barking")

d = Dog()
d.eat()
d.bark()

In this example, Dog inherits the eat() method from Animal. The child class gets access to the parent behavior automatically, so only the additional method bark() needs to be written.

Initializing Parent and Child Classes

Inheritance becomes more important when both the parent and child classes maintain their own data. In that case, the child constructor often needs to call the parent constructor so the inherited part of the object is initialized properly. Python provides super() for this purpose.

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

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

e = Employee("Riya", 105)
print(e.name)
print(e.employee_id)

The call to super().__init__(name) transfers control to the parent constructor. This avoids rewriting the parent initialization logic and keeps the class hierarchy easier to maintain.

Method Overriding in Python

A child class can redefine a method inherited from the parent. This is called method overriding. It is used when the child should keep the same general interface but perform the work differently.

class Shape:
    def area(self):
        print("Area formula depends on the shape")

class Circle(Shape):
    def area(self):
        print("Area = pi * r * r")

s = Shape()
c = Circle()
s.area()
c.area()

Overriding is useful because the parent can define a broad contract while each child class provides its own version. This is one of the foundations of polymorphic design.

Types of Inheritance in Python

TypeMeaningExample Idea
SingleOne child inherits from one parentDog inherits Animal
MultilevelA class inherits from a derived classManager inherits Employee
HierarchicalMultiple child classes inherit from one parentCar and Bike inherit Vehicle
MultipleOne child inherits from more than one parentTeachingAssistant inherits Student and Employee

Python supports all these forms. The syntax is straightforward, but the design should stay disciplined. Multiple inheritance is powerful, though it can make code harder to reason about if classes are mixed without a clear responsibility boundary.

Multiple Inheritance Example

class Camera:
    def capture(self):
        print("Capturing image")

class Phone:
    def call(self):
        print("Calling contact")

class SmartPhone(Camera, Phone):
    pass

sp = SmartPhone()
sp.capture()
sp.call()

Here, SmartPhone inherits features from both Camera and Phone. Python resolves method access using a defined lookup order, which becomes important when parent classes contain methods with the same name.

Method Resolution Order (MRO)

When multiple inheritance is involved, Python follows a method resolution order to decide which class should be searched first. You can inspect it with __mro__ or the mro() method. Understanding MRO helps explain why one parent method is called instead of another.

class A:
    def show(self):
        print("A")

class B(A):
    pass

class C(A):
    pass

class D(B, C):
    pass

print(D.__mro__)

The MRO ensures a predictable search path through the class hierarchy. Without that rule, multiple inheritance would be ambiguous and unreliable.

When to Use Inheritance

  • Use inheritance when classes have a real is-a relationship.
  • Keep shared logic in the parent class and specialized logic in child classes.
  • Prefer composition when classes only need to use another object rather than become a specialized version of it.
  • Do not create deep hierarchies unless the design stays easy to understand.

A strong inheritance design improves reuse and readability. A weak inheritance design creates tight coupling, confusing dependencies, and hard-to-test code. The key is to inherit for genuine abstraction, not just to avoid typing a few repeated lines.

Common Mistakes in Inheritance

A common beginner mistake is forgetting to initialize the parent portion of the object when the child defines its own constructor. Another mistake is overriding methods without preserving the parent contract. For example, if a parent method expects one kind of behavior and a child changes it completely, the class hierarchy becomes harder to trust.

Another mistake is using inheritance where composition would be cleaner. If one class simply uses another class as a helper, inheritance is usually the wrong relationship. Inheritance should model specialization, while composition should model collaboration between objects.

Multilevel and Hierarchical Inheritance

Python also supports multilevel inheritance, where one child becomes the parent of another class, and hierarchical inheritance, where multiple child classes inherit from the same base class. These forms are useful when a broad concept needs to branch gradually into more specialized types. The benefit is that common behavior stays centralized while deeper classes can refine what they inherit.

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

class Car(Vehicle):
    pass

class ElectricCar(Car):
    def charge(self):
        print("Charging battery")

ec = ElectricCar()
ec.start()
ec.charge()

In this chain, ElectricCar receives behavior through Car from Vehicle. That is the core idea of multilevel inheritance.

Using super() with Overridden Methods

The super() function is not limited to constructors. It can also call a parent method from an overridden child method. This is useful when the child wants to extend parent behavior instead of replacing it completely.

class Logger:
    def show(self):
        print("Base log message")

class FileLogger(Logger):
    def show(self):
        super().show()
        print("Writing log to file")

f = FileLogger()
f.show()

This pattern keeps the inherited contract intact while still letting the child add its own extra work.

Inheritance vs Composition

A common design decision is whether one class should inherit from another or simply contain another object. Inheritance should be used when the child is genuinely a specialized form of the parent. Composition should be used when one object mainly uses another object as a helper or component.

  • Use inheritance for a real is-a relationship.
  • Use composition when one object has or uses another object.
  • Prefer simpler composition if inheritance creates an artificial hierarchy.
  • Choose the model that keeps responsibilities clearer and easier to test.

For example, a Car is a Vehicle, so inheritance can make sense. But a Car has an Engine, so composition is often the right design for that relationship.

Common Mistakes in Python Inheritance

  • Creating inheritance chains just to reuse a few lines of code.
  • Overriding methods without preserving the expected meaning of the parent method.
  • Forgetting to call the parent constructor when inherited data still needs initialization.
  • Using multiple inheritance without understanding MRO.
  • Choosing inheritance when composition would produce a cleaner design.

These mistakes usually show up later as rigid code, surprising behavior, and classes that are hard to reuse safely. The quality of inheritance depends more on design judgment than on syntax.

Inheritance in Python Interview Points

For interviews, you should know the meaning of parent and child classes, how inheritance syntax works, how method overriding changes behavior, why super() is used, what multiple inheritance means, and why MRO matters in Python.

What is inheritance in Python? Inheritance is the process by which a child class reuses and extends the behavior of a parent class.

Why is super() used in inheritance? super() is used to call methods from the parent class, especially constructors and overridden methods.

What is MRO in Python? MRO stands for Method Resolution Order, which is the rule Python uses to decide the order of class lookup in inheritance hierarchies.

Practical Use Cases of Inheritance

Inheritance is often used in frameworks, business applications, device models, and UI systems where several objects share a stable base structure. A base class may define common validation, logging, formatting, or communication behavior, while child classes customize only the domain-specific part. This reduces repeated code and gives the codebase one central place to improve shared behavior later.

That said, inheritance should stay focused. If a child class keeps inheriting methods it does not really need, or if the parent becomes a dumping ground for unrelated features, the design starts fighting the program instead of helping it. Strong inheritance design comes from choosing a narrow, meaningful abstraction and letting derived classes extend it carefully.

Choosing a Good Parent Class

A good parent class should represent behavior that is truly common and stable across all derived classes. If the base class becomes too broad, child classes start inheriting methods and assumptions that do not fit them well. That usually leads to condition-heavy code and awkward overrides. A smaller, clearer parent class is usually better than a large parent that tries to predict every possible future child.

When you review an inheritance design, ask whether every child can honestly be described as a specialized form of the parent. If the answer is weak, composition is often the better tool. This one design check prevents many poor hierarchies.