Python • Object-Oriented Programming

Inheritance & Method Overriding — Use inheritance and method overriding (intro)

Learn how classes can share and customize behavior by inheriting from parent classes and overriding their methods.

Code braces with terminal and markup symbols representing Python programming.
Inside this lesson 10 sections 5 questions 8 figures

SECTION 1

Historical Context & Motivation

As software projects grew larger through the 1960s and 1970s, programmers realized that copying and pasting the same code into dozens of files was a recipe for bugs and confusion. If you needed to change how a single feature worked, you might have to update it in twenty different places. Inheritance was invented as a way for one class to automatically receive the attributes and methods of another, so shared behavior could live in just one place. This single idea became one of the pillars of object-oriented programming (OOP).

1967 • Simula Introduces Inheritance

Norwegian computer scientists Ole-Johan Dahl and Kristen Nygaard created Simula, the first language to support classes and inheritance. They wanted to model real-world objects such as ships and passengers in simulations.

1972 • Smalltalk Refines OOP

Alan Kay and his team at Xerox PARC built Smalltalk, a fully object-oriented language where every value is an object. Smalltalk popularized the idea of sending messages to objects and overriding behavior in subclasses.

1991 • Python Is Released

Guido van Rossum released Python 0.9.0, which already included classes with inheritance. Python's clean syntax made OOP accessible to beginners and professionals alike.

2008 • Python 3 Modernizes Classes

Python 3 unified old-style and new-style classes so that every class implicitly inherits from the built-in object class. This made inheritance behavior more consistent and predictable.

The core question that inheritance answers is simple: how do we reuse existing code without duplicating it? And the follow-up question — how do we customize that reused code for specific needs? — is where method overriding enters the picture. Together, these two concepts let you build hierarchies of related classes that share a common foundation while each adding their own twist.

SECTION 2

Core Principles & Definitions

Before writing any code, you need to understand a handful of foundational ideas. These principles explain what inheritance and method overriding are, why they matter, and how they connect to the broader world of object-oriented programming.

1

Parent (Base) Class

The class that holds shared attributes and methods. Other classes inherit from it. In Python, every class ultimately inherits from the built-in object class.
2

Child (Derived) Class

A class that inherits from a parent. It automatically gains all of the parent's attributes and methods, and it can add new ones or change existing ones.
3

Inheritance

The mechanism by which a child class receives code from a parent class. You declare it in Python by placing the parent's name in parentheses: class Child(Parent):
4

Method Overriding

When a child class defines a method with the same name as one in its parent, the child's version replaces the parent's version for that child. This is how you customize inherited behavior.
5

super()

A built-in Python function that lets a child class call the parent's version of a method. This is useful when you want to extend the parent's behavior rather than completely replace it.
✦

KEY TAKEAWAY

Think of inheritance like a family recipe. Your grandparent writes the original recipe (the parent class). You inherit that recipe automatically — you know every ingredient and step. Method overriding is when you decide to swap out one ingredient or change a step to suit your own taste, while keeping everything else the same. And super() is like saying, "Do everything grandma did, and then add my extra step on top."

SECTION 3

Visual Explanation — Class Hierarchy Diagram

The diagram below shows a classic inheritance hierarchy. A parent class called Animal defines shared attributes (name, sound) and a method called speak(). Two child classes — Dog and Cat — inherit from Animal and override speak() to produce their own unique output.

Animal (Parent)name: strsound: strdef speak(self)inheritsinheritsDog (Child)breed: strdef speak(self) ✦→ "Woof! My name is ..."Cat (Child)indoor: booldef speak(self) ✦→ "Meow! My name is ..."✦ = overridden method
The Animal parent class (purple border) defines speak(). The Dog and Cat child classes each override speak() (marked with ✦) to produce different output. Both children also add their own unique attributes.

Notice the arrows pointing from the parent to the children. In UML (Unified Modeling Language) diagrams used by professional developers, these arrows are drawn the other way — from child to parent — to show that the child "depends on" the parent. For learning purposes, the downward arrows here emphasize that behavior flows from parent to child. Each child automatically receives name and sound without re-declaring them, but each child overrides speak() to customize it.

SECTION 4

How Inheritance & Overriding Work Under the Hood

The Method Resolution Order (MRO)

When you call a method on an object, Python doesn't just look in the object's class. It follows a specific search path called the Method Resolution Order (MRO). Python first checks the object's own class for the method. If it doesn't find the method there, it moves up to the parent class, then the grandparent class, and so on until it either finds the method or raises an AttributeError. This is why overriding works: because Python finds the child's version of a method before it ever reaches the parent's version.

Syntax for Inheritance

INHERITANCE SYNTAX

classChildClass(ParentClass):class ChildClass(ParentClass):

ChildClass — the new class being created. ParentClass — the existing class placed in parentheses. The child automatically inherits every attribute and method from the parent.

Syntax for Method Overriding

OVERRIDING PATTERN

def method_name(self): # same name as in parent

Define a method inside the child class with exactly the same name as the parent's method. Python will use the child's version whenever this method is called on a child object.

Using super() to Extend, Not Replace

SUPER() PATTERN

super().__init__(args) # calls parent's __init__

super() returns a temporary object of the parent class, allowing you to call the parent's method. This is especially common in __init__ so the child can set up the parent's attributes and then add its own.

💡

Why super() Matters

Without super(), a child's __init__ would completely replace the parent's __init__, meaning none of the parent's attributes would be created. Using super().__init__() ensures the parent's setup code runs first, and then the child adds its own attributes on top.

SECTION 5

Detailed Code Walkthrough

Let's look at a complete, runnable Python example that demonstrates inheritance and method overriding step by step. The diagram below traces what happens when you create a Dog object and call its speak() method.

Execution Flow: Creating a Dog and Calling speak()class Animal:def __init__(self, name, sound):self.name = nameself.sound = sounddef speak(self):return f"{self.name} says {self.sound}"← parent's speak() — genericclass Dog(Animal):def __init__(self, name, breed):super().__init__(name, "Woof")self.breed = breeddef speak(self): # overrides!return f"{self.name} the {self.breed} says Woof!"← child's speak() — customizedEXECUTION STEPS1rex = Dog("Rex", "Labrador")2Dog.__init__ runs →calls super().__init__3Animal.__init__ setsname="Rex", sound="Woof"4Dog.__init__ then setsbreed="Labrador"5rex.speak() → Pythonfinds Dog.speak first!✓Output:"Rex the Labrador says Woof!"super()
This diagram traces the five steps of creating a Dog object and calling speak(). The green dashed line shows super() reaching up to the Animal parent class. Step 5 demonstrates the MRO: Python finds Dog.speak() before it ever checks Animal.speak().

Pay close attention to step 2 in the diagram. When Dog.__init__ calls super().__init__(name, "Woof"), it passes "Woof" as the sound argument. This means every Dog automatically gets sound = "Woof" without the caller needing to specify it. The child class simplifies the interface while still relying on the parent to do the heavy lifting.

Attribute / MethodDefined InAvailable On Dog?
nameAnimal (parent)✅ Yes — inherited
soundAnimal (parent)✅ Yes — inherited
breedDog (child)✅ Yes — own attribute
speak()Both (overridden in Dog)✅ Uses Dog's version

Summary of attributes and methods available on a Dog object

SECTION 6

Worked Example — Building a Vehicle Hierarchy

Let's build a fresh example from scratch to solidify your understanding. We'll create a Vehicle parent class and an ElectricCar child class that overrides a method.

Vehicle Inheritance Example

  1. 1

    Step 1 — Define the Parent Class

    We start by creating a Vehicle class with an __init__ method that stores make, model, and year. We also add a fuel_type() method that returns "Gasoline" by default.
    class Vehicle: with __init__ and fuel_type()
  2. 2

    Step 2 — Define the Child Class with Inheritance

    We write class ElectricCar(Vehicle): to make ElectricCar a child of Vehicle. In its __init__, we call super().__init__(make, model, year) so the parent's setup runs first. Then we add a new attribute, battery_kwh.
    super().__init__(make, model, year) inherits parent setup; self.battery_kwh is new
  3. 3

    Step 3 — Override the fuel_type() Method

    Inside ElectricCar, we define fuel_type(self) and return "Electric" instead of "Gasoline". Because the method has the same name as the parent's, Python will use this version for all ElectricCar objects.
    fuel_type() now returns "Electric" for ElectricCar instances
  4. 4

    Step 4 — Create an Object and Test

    We create an object: my_car = ElectricCar("Tesla", "Model 3", 2024, 75). When we call my_car.fuel_type(), Python checks ElectricCar first, finds the overridden method, and returns "Electric". The Vehicle version is never reached.
    my_car.fuel_type() → "Electric"
  5. 5

    Step 5 — Verify Inherited Attributes

    We can also access my_car.make, my_car.model, and my_car.year — all inherited from Vehicle via super(). Plus, my_car.battery_kwh equals 75, which is unique to the child class.
    All parent attributes plus child-specific battery_kwh = 75 are available

SECTION 7

Strengths, Limitations & Common Pitfalls

Inheritance is a powerful tool, but like any tool it can be misused. Understanding both its strengths and its pitfalls will help you decide when to use it and when a different approach might be better.

Strengths ✅Limitations / Pitfalls ⚠️
Code Reuse — Write shared logic once in the parent; all children get it automatically.Tight Coupling — Changing the parent can break child classes if they depend on specific internal details.
Consistency — Related classes share the same interface, making code predictable.Deep Hierarchies — A chain of five or more parent-child levels becomes confusing and hard to debug.
Extensibility — Add new child classes without modifying the parent.Fragile Base Class — A small change in the parent's method signature can silently break overriding in children.
Polymorphism — Different child objects can be used interchangeably wherever the parent type is expected.Forgetting super() — If you override __init__ without calling super(), parent attributes won't be initialized.

Strengths vs. limitations of inheritance

🔧

DESIGN GUIDELINE

A good rule of thumb is the "is-a" test. Only use inheritance when the child truly "is a" type of the parent. A Dog is-a Animal — that works. But a Wheel is-not-a Car; a Car has Wheels. If the relationship is "has-a" instead of "is-a", use composition (storing objects as attributes) rather than inheritance.

SECTION 8

Connection to Advanced OOP Concepts

The single-inheritance pattern you learned today is just the beginning. As you continue studying Python, you'll encounter more advanced techniques that build directly on these foundations. Here is a quick preview of how today's concepts connect to what comes next.

What You Know NowWhat Comes NextKey Difference
Single inheritance (one parent)Multiple inheritance (two or more parents)A child can inherit from several classes at once: class C(A, B):
Method overriding (replace parent method)Abstract methods (force child to override)Using ABC module, the parent can require children to implement a method.
Using super() in __init__MRO with super() in diamond hierarchiesWhen multiple parents share a grandparent, Python's C3 linearization determines the call order.
Inheritance ("is-a" relationship)Composition ("has-a" relationship)Instead of inheriting, a class stores other objects as attributes. Often preferred for flexibility.

How today's concepts connect to advanced OOP topics

Don't worry about mastering all of these right now. The important thing is that single inheritance and method overriding form the foundation for every one of these advanced patterns. Once you feel confident creating parent and child classes, overriding methods, and using super(), you'll be ready to tackle any of these topics.

SECTION 9

Practice Problems

Question 1: CONCEPTUAL

In your own words, explain the difference between inheriting a method and overriding a method. Why would a child class want to override a method rather than simply use the parent's version?
Show answer
Inheriting a method means the child class uses the parent's method exactly as written — no changes are needed. Overriding a method means the child class defines its own version with the same name, which replaces the parent's version for that child. A child would want to override when the parent's behavior is too generic. For example, an Animal.speak() method might return a generic message, but a Dog needs to return something specific like "Woof!" The child can customize behavior while still inheriting everything else.

Question 2: BASIC CALCULATION

Given the following code, what will be printed? class Shape: def area(self): return 0 class Square(Shape): def __init__(self, side): self.side = side def area(self): return self.side * self.side s = Square(5) print(s.area())
Show answer
The output is 25. When s.area() is called, Python checks the Square class first. It finds area() defined there, so it uses self.side * self.side which is 5 × 5 = 25. The parent's area() that returns 0 is never reached because the child's override takes priority.

Question 3: INTERMEDIATE

Write a Student class that inherits from a Person class. Person has __init__(self, name, age) and a method greet() that returns "Hi, I'm [name]." The Student class should add a grade attribute and override greet() to return "Hi, I'm [name] and I'm in grade [grade]." Use super() in __init__.
Show answer
Here is one solution: class Person: def __init__(self, name, age): self.name = name self.age = age def greet(self): return f"Hi, I'm {self.name}." class Student(Person): def __init__(self, name, age, grade): super().__init__(name, age) self.grade = grade def greet(self): return f"Hi, I'm {self.name} and I'm in grade {self.grade}." The key decisions are: (1) super().__init__(name, age) lets Person set up name and age; (2) self.grade = grade adds the new attribute; (3) greet() is overridden to include grade information.

Question 4: APPLIED

You are building a game. You have a Character class with __init__(self, name, hp) and a method attack(self) that returns 10 (base damage). Create a Mage subclass that adds a mana attribute and overrides attack() so it returns 10 + self.mana // 5 (base damage plus a bonus based on mana, using integer division). If a Mage has 60 mana, what does attack() return?
Show answer
The Mage.attack() returns 22. The calculation is 10 + 60 // 5 = 10 + 12 = 22. Here is the code: class Character: def __init__(self, name, hp): self.name = name; self.hp = hp def attack(self): return 10 class Mage(Character): def __init__(self, name, hp, mana): super().__init__(name, hp); self.mana = mana def attack(self): return 10 + self.mana // 5 The Mage inherits name and hp from Character via super(), adds its own mana, and overrides attack() to factor in magical power.

Question 5: CRITICAL THINKING

A classmate writes a Penguin class that inherits from a Bird class. The Bird class has a fly() method that returns "Soaring through the sky!" Your classmate overrides fly() in Penguin to return "I can't fly!" Is this a good use of inheritance and overriding? What design problems might this create? Suggest an alternative approach.
Show answer
This works technically — Python won't complain — but it's a questionable design. The problem is that any code expecting a Bird to fly will break logically when it receives a Penguin. This violates the Liskov Substitution Principle, which says a child should be usable anywhere its parent is expected without surprises. A better approach would be to restructure the hierarchy: create a general Bird class without a fly() method, and then create a FlyingBird subclass that adds fly(). Penguin would inherit from Bird (no fly method), while Eagle would inherit from FlyingBird. This avoids overriding a method just to disable it.

SUMMARY

Lesson Summary

Summary

Inheritance lets a child class automatically receive all the attributes and methods of a parent class, enabling code reuse and consistent interfaces. You declare it by writing class Child(Parent):. Method overriding allows a child class to replace an inherited method with its own version by defining a method with the same name. Python's Method Resolution Order (MRO) searches the child class first, which is why the override takes priority.

The built-in super() function is essential when you want to extend rather than fully replace a parent's method — especially in __init__ where the parent sets up shared attributes. Always apply the "is-a" test before using inheritance: if the child truly "is a" type of the parent, inheritance is appropriate. With these tools, you can build clean, organized class hierarchies that are easy to read, maintain, and extend.

Varsity Tutors • Python • Inheritance & Method Overriding