Inheritance in Python: A Practical Tutorial with Examples
Learn how Python classes inherit state and behaviour through runnable examples. Trace super(), overrides, MRO, common mistakes, and three focused exercises.
KnowledgeGate Team
Exam prep & CS education

You can write a Python class, but reusing its state and behaviour in another class can still feel unclear. Copying methods works briefly, then creates two versions to maintain. Inheritance lets a child class reuse a base class, while overriding and super() control what changes. Method resolution order decides which implementation Python calls when more than one parent is involved.
What inheritance means in Python, and when it fits
Inheritance creates a relationship in which a child class receives accessible attributes and methods from a parent class. The child can use that behaviour unchanged, extend it, or replace selected methods. When you access a method, Python first checks the object's class, then follows its parent classes in method resolution order (MRO).
Use inheritance when the sentence “a child is a parent” makes sense. A PythonCourse is a Course, so inheritance fits. A Course has a Lesson, so that relationship should normally use composition: store a lesson object inside the course. This distinction prevents forced class hierarchies. Inheritance is one useful technique within the broader foundation covered by Coding & Skills courses, but it is not the answer to every reuse problem.
Your first base class and child class
class Device:
def __init__(self, label):
self.label = label
def identify(self):
return f"Device: {self.label}"
class Laptop(Device):
pass
item = Laptop("Lab-07")
print(item.identify())
print(isinstance(item, Device))Device: Lab-07
TrueThe syntax class Laptop(Device): declares Laptop as a child of Device. Laptop defines neither __init__ nor identify, so Python finds both on Device. The inherited initialiser stores "Lab-07" on the new object, and the inherited method reads it. The object remains a Laptop, while isinstance also recognises it as a Device. The pass statement is valid because the class body cannot be empty; here it says that the child adds no behaviour yet. That means the child starts useful without duplicating code. You can add Laptop methods later, while shared Device behaviour remains in one place rather than becoming two copies that need separate maintenance.
Worked example: extend a parent with super() and override a method
Course owns state shared by every course. PythonCourse adds a project count and specialises the progress message.
class Course:
def __init__(self, title, lessons):
self.title = title
self.lessons = lessons
def progress(self, completed):
return f"{completed}/{self.lessons} lessons"
class PythonCourse(Course):
def __init__(self, title, lessons, projects):
super().__init__(title, lessons)
self.projects = projects
def progress(self, completed):
base = super().progress(completed)
percent = completed / self.lessons * 100
return f"{base} ({percent:.0f}%), projects: {self.projects}"
course = PythonCourse("Python Foundations", 24, 3)
print(course.title)
print(course.progress(18))
print(isinstance(course, Course))Python Foundations
18/24 lessons (75%), projects: 3
TrueFollow the values step by step. super().__init__ continues lookup to Course.__init__, which stores title="Python Foundations" and lessons=24. The child then stores projects=3.
Calling course.progress(18) selects the child's override. Its super().progress(18) call obtains the parent string 18/24 lessons. The calculation is 18 / 24 * 100 = 0.75 * 100 = 75. The :.0f format displays that value with no decimal places. The child combines the inherited result with its own percentage and project data. If you want structured practice after retyping the example, the Python programming course is an optional next step.

The five inheritance shapes you should recognise
Python supports five commonly named inheritance shapes:
Shape | Exact relationship | What it demonstrates |
|---|---|---|
Single |
| One child and one direct parent |
Multilevel |
| Inheritance across multiple levels |
Hierarchical |
| Several children share one parent |
Multiple |
| One child has multiple direct parents |
Hybrid |
| Two or more shapes are combined |
These labels describe structures, not reasons to build them. Keep the “is-a” test and a clear lookup path as the design criteria. Every user-defined class ultimately inherits from object, but that fact does not turn object -> Course -> PythonCourse into an intentional multilevel design.
Method overriding, multiple inheritance, and MRO
Overriding means defining a child method with the same name as a parent method. PythonCourse.progress wins because lookup checks the child first. Its super() call then deliberately continues after PythonCourse in the MRO.
class Logger:
def describe(self):
return "logger"
class Repository:
def describe(self):
return "repository"
class CachedRepository(Logger, Repository):
pass
repo = CachedRepository()
print(repo.describe())
print([cls.__name__ for cls in CachedRepository.__mro__])logger
['CachedRepository', 'Logger', 'Repository', 'object']The parent order in class CachedRepository(Logger, Repository) produces that MRO, so Logger.describe is the first match. Reversing the parents makes Repository.describe win. When several classes cooperate, design their methods to call super() with compatible arguments. Directly naming a parent inside reusable multiple-inheritance code can skip another class in the MRO and break that cooperation.

Common inheritance errors and the fix for each
Each common failure has a direct repair:
Missing parent initialisation: omitting
super().__init__leavestitleandlessonsunset, socourse.progress(18)raisesAttributeError. Callsuper().__init__(title, lessons).Wrong initialiser arguments: passing a missing or extra argument to the parent raises
TypeError. Match the parent's method signature.Accidental recursion:
return self.progress(completed)inside the override selects the same override repeatedly and ends inRecursionError. Callsuper().progress(completed).Accessing
__lessonsdirectly: a double-leading underscore triggers name mangling. If subclass access is intentional, expose a property or use a documented_lessonsconvention.Using inheritance for “has-a”: making
Lessona parent ofCoursegives the model the wrong meaning. Store a containedLessonobject instead.
Read the exception from the first missing attribute or bad call, then repair the hierarchy instead of adding unrelated defaults. A small failing example usually exposes the design error faster.
How interviews and exams test inheritance, plus three exercises
Stable question forms ask you to predict output from an override, identify the first method chosen by an MRO, or debug a missing parent initialiser. Practise those skills without assuming any particular exam pattern. Before running code, write the expected lookup path beside each call. This habit separates method selection from method execution and makes MRO questions much easier to debug under pressure.
Create
VideoCourse(Course)withlessons=16andquality="1080p". Makeprogress(12)return12/16 lessons (75%), quality: 1080p. Check the arithmetic:12 / 16 * 100 = 75.Predict both output lines from
CachedRepository. Reverse its parents and confirm that the first line becomesrepository.Remove
super().__init__fromPythonCourse, observe theAttributeError, restore the call, and reproduce18/24 lessons (75%), projects: 3.
For more code tracing, compare control flow in the sorting algorithms comparison and the dynamic programming worked example. Once the OOP basics are comfortable, the DSA Using Python course offers a structured move into problem-solving practice.
Inheritance in Python: the short version and next step
Use inheritance for a genuine is-a relationship. Initialise shared state through super(), override only what differs, inspect ClassName.__mro__ when multiple parents are involved, and choose composition for has-a relationships. Now retype the Course example with lessons=30, completed=21, and projects=4. Since 21 / 30 * 100 = 70, the message must be 21/30 lessons (70%), projects: 4. Use the printed result as your checkpoint. Verify it, then attempt the three exercises.
Keep learning

Pandas Basics in Python: Build, Clean and Analyse a DataFrame Step by Step
Follow one student dataset from its first DataFrame to a clean city summary, while learning how selection, missing values and vectorised calculations really work.

Python Operators and Expressions: Precedence, Types and Worked Output Traces
Trace Python expressions without guessing. This guide connects operator families, precedence, types, short-circuiting and exact output through worked examples.

CSV Files Explained: Parsing Rules, Worked Records and Exam Traps
Learn why commas and newlines are not always boundaries, trace a quote-aware parser, validate text fields with a schema, and calculate processing costs.

Polymorphism and Dunder Methods in Python: Runnable Examples and Exercises
See how one Python operation supports different types, then build a Vector2D class with readable output, addition, magnitude and equality. Includes runnable code, protocol failures and exercises.