- What is hierarchical inheritance — one parent class with many children
- How it works — understanding the one-to-many relationship
- Sharing common features — how children reuse parent code
- Each child is unique — adding specific features to each child
- Constructors — initializing parent and child classes
- Real-world use — practical examples you can use
What is Hierarchical Inheritance?
Hierarchical inheritance is a type of inheritance where a single parent class is inherited by many child classes. Think of it like a family tree where one parent has many children. Each child gets the parent's traits, but each child is also unique and can have its own special qualities.
Imagine a school teacher who teaches many students. All students learn the same basic lessons from the teacher (parent class), but each student has their own personality, interests, and skills (child classes). This is exactly how hierarchical inheritance works in Python.
This is different from multilevel inheritance, where you have a chain like Grandparent → Parent → Child. In hierarchical inheritance, you have one parent and many children directly under it.
💡 Key concept: Hierarchical inheritance is like a tree — one parent class at the root, with many child classes branching out from it. Each child inherits the parent's features but can also have its own unique features.
One Parent, Many Children
Understanding the One-to-Many Relationship
In hierarchical inheritance, you have one parent class that is the base for many child classes. Each child class inherits everything from the parent, but each child can also add its own methods and attributes.
# Hierarchical inheritance - One parent, many children
# Parent class (the base)
class Animal:
"""The parent class - common to all animals"""
def __init__(self, name, age):
self.name = name
self.age = age
def eat(self):
return f"{self.name} is eating"
def sleep(self):
return f"{self.name} is sleeping"
def sound(self):
return f"{self.name} makes a sound"
# Child class 1 - Dog
class Dog(Animal):
"""Dog is a child of Animal"""
def sound(self):
return f"{self.name} says Woof!"
def fetch(self):
return f"{self.name} is fetching the ball"
# Child class 2 - Cat
class Cat(Animal):
"""Cat is a child of Animal"""
def sound(self):
return f"{self.name} says Meow!"
def purr(self):
return f"{self.name} is purring"
# Child class 3 - Bird
class Bird(Animal):
"""Bird is a child of Animal"""
def sound(self):
return f"{self.name} says Chirp!"
def fly(self):
return f"{self.name} is flying"
# Creating objects
print("=== DOG ===")
dog = Dog("Buddy", 3)
print(dog.eat()) # Buddy is eating (from parent)
print(dog.sound()) # Buddy says Woof! (overridden)
print(dog.fetch()) # Buddy is fetching the ball (specific to Dog)
print("\n=== CAT ===")
cat = Cat("Whiskers", 2)
print(cat.eat()) # Whiskers is eating (from parent)
print(cat.sound()) # Whiskers says Meow! (overridden)
print(cat.purr()) # Whiskers is purring (specific to Cat)
print("\n=== BIRD ===")
bird = Bird("Tweety", 1)
print(bird.eat()) # Tweety is eating (from parent)
print(bird.sound()) # Tweety says Chirp! (overridden)
print(bird.fly()) # Tweety is flying (specific to Bird)
# Checking the relationship
print("\n=== RELATIONSHIP ===")
print(f"Is Dog an Animal? {issubclass(Dog, Animal)}") # True
print(f"Is Cat an Animal? {issubclass(Cat, Animal)}") # True
print(f"Is Bird an Animal? {issubclass(Bird, Animal)}") # True
print(f"Is Dog a Cat? {issubclass(Dog, Cat)}") # False
One parent, many children key points:
- One parent — a single base class at the top
- Many children — multiple classes that inherit from the same parent
- Shared features — all children get the parent's methods and attributes
- Unique features — each child can add its own methods
- Independent children — each child stands on its own
Quick Check: How many parent classes are there in hierarchical inheritance? (Answer: One parent class, with many child classes)
How Hierarchical Inheritance Works
Understanding the Mechanism
When you use hierarchical inheritance, each child class gets a copy of the parent's methods and attributes. Each child can use the parent's methods as they are, or they can override them to change the behavior. This is called method overriding.
The children are completely independent of each other. Changes made to one child don't affect the other children. They all share the common code from the parent, but each child can go its own way.
# How hierarchical inheritance works
class Vehicle:
"""Parent class - common vehicle features"""
def __init__(self, brand, color):
self.brand = brand
self.color = color
self.speed = 0
def start(self):
self.speed = 10
return f"{self.brand} is starting. Speed: {self.speed} km/h"
def stop(self):
self.speed = 0
return f"{self.brand} stopped"
def accelerate(self):
self.speed += 10
return f"{self.brand} speeding up. Speed: {self.speed} km/h"
def honk(self):
return f"{self.brand} honks!"
# Child class 1 - Car
class Car(Vehicle):
"""Car is a vehicle"""
def __init__(self, brand, color, doors=4):
super().__init__(brand, color) # Call parent constructor
self.doors = doors
# Override the honk method
def honk(self):
return f"{self.brand} car says Beep Beep!"
# Car-specific method
def open_trunk(self):
return f"{self.brand} trunk is open"
# Child class 2 - Bike
class Bike(Vehicle):
"""Bike is a vehicle"""
def __init__(self, brand, color, has_basket=False):
super().__init__(brand, color)
self.has_basket = has_basket
# Override the honk method differently
def honk(self):
return f"{self.brand} bike says Ring Ring!"
# Bike-specific method
def pedal(self):
return f"Pedaling the {self.brand} bike"
# Child class 3 - Truck
class Truck(Vehicle):
"""Truck is a vehicle"""
def __init__(self, brand, color, load_capacity=1000):
super().__init__(brand, color)
self.load_capacity = load_capacity
# Override the honk method differently
def honk(self):
return f"{self.brand} truck says HOOONK!"
# Truck-specific method
def load_cargo(self, weight):
if weight <= self.load_capacity:
return f"Loading {weight}kg cargo into {self.brand} truck"
else:
return f"Too heavy! {self.brand} can only carry {self.load_capacity}kg"
# Testing
print("=== CAR ===")
car = Car("Toyota", "Red", 4)
print(car.start()) # Parent method
print(car.honk()) # Overridden
print(car.open_trunk()) # Car-specific
print("\n=== BIKE ===")
bike = Bike("Hero", "Blue", True)
print(bike.start()) # Parent method
print(bike.honk()) # Overridden
print(bike.pedal()) # Bike-specific
print("\n=== TRUCK ===")
truck = Truck("Volvo", "White", 2000)
print(truck.start()) # Parent method
print(truck.honk()) # Overridden
print(truck.load_cargo(1500)) # Truck-specific
print("\n=== ALL CHILDREN ARE INDEPENDENT ===")
print(f"Car has {car.doors} doors")
print(f"Bike has basket? {bike.has_basket}")
print(f"Truck can carry {truck.load_capacity}kg")
How hierarchical inheritance works:
- Children inherit — all child classes get parent methods
- Method overriding — children can change parent methods
- Independent children — changes in one child don't affect others
- Child-specific features — each child can add its own methods
- Shared code — common code is in the parent class only
Quick Check: When you change a method in one child class, does it affect other child classes? (Answer: No, each child is independent)
Sharing Common Features
Reusing Code with the Parent Class
The biggest advantage of hierarchical inheritance is code reuse. Instead of writing the same code in every child class, you write it once in the parent class, and all children get it automatically.
This saves you time and makes your code cleaner. If you need to change something that's common to all children, you only change it in one place — the parent class.
# Sharing common features in hierarchical inheritance
class Employee:
"""Parent class - common employee features"""
def __init__(self, emp_id, name, salary):
self.emp_id = emp_id
self.name = name
self.salary = salary
self.attendance = []
def mark_attendance(self, date):
self.attendance.append(date)
return f"{self.name} marked attendance for {date}"
def get_salary(self):
return f"{self.name}'s monthly salary: ${self.salary}"
def work(self):
return f"{self.name} is working"
def take_break(self, minutes):
return f"{self.name} took a {minutes} minute break"
# Child class 1 - Developer
class Developer(Employee):
"""Developer is an employee"""
def __init__(self, emp_id, name, salary, programming_language):
super().__init__(emp_id, name, salary)
self.programming_language = programming_language
self.code_written = 0
# Override work method
def work(self):
self.code_written += 100
return f"{self.name} is coding in {self.programming_language}"
# Developer-specific method
def debug_code(self):
return f"{self.name} is debugging code"
# Child class 2 - Manager
class Manager(Employee):
"""Manager is an employee"""
def __init__(self, emp_id, name, salary, team_size=0):
super().__init__(emp_id, name, salary)
self.team_size = team_size
self.meetings = 0
# Override work method
def work(self):
self.meetings += 1
return f"{self.name} is leading a team of {self.team_size} people"
# Manager-specific method
def conduct_meeting(self, topic):
self.meetings += 1
return f"{self.name} is conducting a meeting about {topic}"
# Child class 3 - Designer
class Designer(Employee):
"""Designer is an employee"""
def __init__(self, emp_id, name, salary, design_tool):
super().__init__(emp_id, name, salary)
self.design_tool = design_tool
self.designs_completed = 0
# Override work method
def work(self):
self.designs_completed += 1
return f"{self.name} is designing with {self.design_tool}"
# Designer-specific method
def create_prototype(self):
self.designs_completed += 1
return f"{self.name} created a new prototype"
# Using the shared features
print("=== DEVELOPER ===")
dev = Developer("D001", "Alice", 80000, "Python")
print(dev.work()) # Overridden
print(dev.get_salary()) # From parent
print(dev.mark_attendance("2026-08-09")) # From parent
print(dev.debug_code()) # Developer-specific
print("\n=== MANAGER ===")
mgr = Manager("M001", "Bob", 120000, 5)
print(mgr.work()) # Overridden
print(mgr.get_salary()) # From parent
print(mgr.mark_attendance("2026-08-09")) # From parent
print(mgr.conduct_meeting("Project Review")) # Manager-specific
print("\n=== DESIGNER ===")
des = Designer("D002", "Carol", 75000, "Figma")
print(des.work()) # Overridden
print(des.get_salary()) # From parent
print(des.mark_attendance("2026-08-09")) # From parent
print(des.create_prototype()) # Designer-specific
print("\n=== SHARED FEATURES ===")
print(f"All employees have: emp_id, name, salary, attendance")
print(f"All employees can: work, get_salary, mark_attendance, take_break")
Sharing features key points:
- Write once, use many times — common code in the parent
- Code reuse — all children inherit parent methods
- Easy maintenance — change the parent to update all children
- Less duplication — don't repeat the same code
- Consistent behavior — all children share the same base functionality
Quick Check: What is the main benefit of putting common code in the parent class? (Answer: Code reuse — write once, use in all children)
Each Child Has Its Own Identity
Adding Unique Features to Each Child
While all children share the parent's features, each child can also have its own unique methods and attributes. This is what makes hierarchical inheritance powerful — you get the best of both worlds: shared code and individual specialization.
# Each child has its own unique features
class Payment:
"""Parent class - common payment features"""
def __init__(self, amount, currency="USD"):
self.amount = amount
self.currency = currency
self.status = "pending"
def process_payment(self):
self.status = "processed"
return f"Processing {self.currency} {self.amount}"
def get_status(self):
return f"Payment status: {self.status}"
def refund(self):
if self.status == "processed":
self.status = "refunded"
return f"Refunding {self.currency} {self.amount}"
else:
return "Cannot refund - payment not processed"
# Child class 1 - CreditCardPayment
class CreditCardPayment(Payment):
"""Credit card payment"""
def __init__(self, amount, card_number, expiry_date, cvv):
super().__init__(amount)
self.card_number = card_number
self.expiry_date = expiry_date
self.cvv = cvv
# Override process_payment
def process_payment(self):
# Validate card
if len(self.card_number) == 16:
self.status = "processed"
return f"Credit card payment of ${self.amount} approved"
else:
self.status = "failed"
return f"Invalid card number: {self.card_number}"
# Unique method
def save_card_for_future(self):
return f"Card ending in {self.card_number[-4:]} saved for future"
# Child class 2 - PayPalPayment
class PayPalPayment(Payment):
"""PayPal payment"""
def __init__(self, amount, email, password):
super().__init__(amount)
self.email = email
self.password = password
# Override process_payment
def process_payment(self):
if "@" in self.email:
self.status = "processed"
return f"PayPal payment of ${self.amount} from {self.email}"
else:
self.status = "failed"
return f"Invalid email: {self.email}"
# Unique method
def get_paypal_balance(self):
return f"Balance for {self.email}: $1,234.56"
# Child class 3 - BankTransferPayment
class BankTransferPayment(Payment):
"""Bank transfer payment"""
def __init__(self, amount, account_number, routing_number):
super().__init__(amount)
self.account_number = account_number
self.routing_number = routing_number
# Override process_payment
def process_payment(self):
if len(self.routing_number) == 9:
self.status = "processed"
return f"Bank transfer of ${self.amount} from account {self.account_number[-4:]}"
else:
self.status = "failed"
return f"Invalid routing number: {self.routing_number}"
# Unique method
def get_transaction_id(self):
return f"Transaction ID: TXN{self.account_number[:6]}"
# Testing
print("=== CREDIT CARD PAYMENT ===")
credit = CreditCardPayment(100.00, "1234567812345678", "12/25", "123")
print(credit.process_payment()) # Overridden
print(credit.get_status()) # From parent
print(credit.save_card_for_future()) # Unique
print("\n=== PAYPAL PAYMENT ===")
paypal = PayPalPayment(75.50, "user@email.com", "pass123")
print(paypal.process_payment()) # Overridden
print(paypal.get_status()) # From parent
print(paypal.get_paypal_balance()) # Unique
print("\n=== BANK TRANSFER ===")
bank = BankTransferPayment(500.00, "987654321", "123456789")
print(bank.process_payment()) # Overridden
print(bank.get_status()) # From parent
print(bank.get_transaction_id()) # Unique
print("\n=== EACH CHILD IS UNIQUE ===")
print("All children share: amount, currency, status, process_payment(), refund()")
print("But each has its own unique methods and attributes")
Each child is unique key points:
- Unique methods — each child can have its own methods
- Unique attributes — each child can have its own properties
- Method overriding — children can change how parent methods work
- Independent — each child can work differently
- Specialization — make each child good at its own thing
Quick Check: Can each child class have its own methods that other children don't have? (Answer: Yes, each child can have unique methods)
Working with Constructors
Initializing Parent and Child Classes
When you create an object from a child class, the child's __init__ method is called first. To properly set up the parent's attributes, you need to call the parent's __init__ method using super().__init__().
This ensures that all attributes from the parent are properly initialized before you add the child's own attributes.
# Working with constructors in hierarchical inheritance
class Product:
"""Parent class - common product features"""
def __init__(self, product_id, name, price):
print(f"Product __init__ called for {name}")
self.product_id = product_id
self.name = name
self.price = price
self.in_stock = True
def display_info(self):
return f"ID: {self.product_id}, Name: {self.name}, Price: ${self.price}"
# Child class 1 - Electronics
class Electronics(Product):
"""Electronic product"""
def __init__(self, product_id, name, price, brand, warranty_years):
print(f"Electronics __init__ called for {name}")
super().__init__(product_id, name, price) # Call parent constructor
self.brand = brand
self.warranty_years = warranty_years
# Child-specific method
def get_warranty_info(self):
return f"{self.name} has {self.warranty_years} years warranty"
# Child class 2 - Clothing
class Clothing(Product):
"""Clothing product"""
def __init__(self, product_id, name, price, size, material):
print(f"Clothing __init__ called for {name}")
super().__init__(product_id, name, price) # Call parent constructor
self.size = size
self.material = material
# Child-specific method
def get_size_guide(self):
return f"{self.name} size {self.size}, made of {self.material}"
# Child class 3 - Book
class Book(Product):
"""Book product"""
def __init__(self, product_id, name, price, author, pages):
print(f"Book __init__ called for {name}")
super().__init__(product_id, name, price) # Call parent constructor
self.author = author
self.pages = pages
# Child-specific method
def get_author_info(self):
return f"Author: {self.author}, Pages: {self.pages}"
# Creating objects
print("=== ELECTRONICS ===")
laptop = Electronics("E001", "Laptop", 999.99, "Dell", 2)
print(laptop.display_info())
print(laptop.get_warranty_info())
print("\n=== CLOTHING ===")
shirt = Clothing("C001", "T-Shirt", 19.99, "L", "Cotton")
print(shirt.display_info())
print(shirt.get_size_guide())
print("\n=== BOOK ===")
book = Book("B001", "Python Guide", 49.99, "John Smith", 350)
print(book.display_info())
print(book.get_author_info())
# Note: If you forget to call super().__init__()
class BrokenProduct(Product):
def __init__(self, product_id, name, price):
# super().__init__(product_id, name, price) # This line is missing
self.discount = 10
# This would cause an error:
# broken = BrokenProduct("B001", "Broken", 100)
# print(broken.name) # AttributeError!
print("\n=== CONSTRUCTOR CHAIN ===")
print("When creating a child object, the constructors run in this order:")
print("1. Child __init__")
print("2. Parent __init__ (via super())")
Constructors key points:
- Call super() — always call
super().__init__()in child classes - Initialize all attributes — both parent and child attributes
- Order matters — parent is initialized before child
- Don't forget — forgetting super() causes errors
- Each child can have different parameters — children can have unique arguments
Quick Check: What happens if you don't call super().__init__() in the child class? (Answer: Parent attributes won't be initialized, causing AttributeError)
Real-World Examples
Seeing Hierarchical Inheritance in Action
# Real-world example: A School Management System
class Person:
"""Parent class - common person features"""
def __init__(self, name, age, address):
self.name = name
self.age = age
self.address = address
print(f"Creating person: {name}")
def get_details(self):
return f"Name: {self.name}, Age: {self.age}, Address: {self.address}"
def celebrate_birthday(self):
self.age += 1
return f"{self.name} is now {self.age} years old"
# Child class 1 - Student
class Student(Person):
"""Student is a person"""
def __init__(self, name, age, address, student_id, grade, subjects):
super().__init__(name, age, address)
self.student_id = student_id
self.grade = grade
self.subjects = subjects
self.grades = {}
print(f"Student created: {name} (ID: {student_id})")
def study(self, subject):
return f"{self.name} is studying {subject}"
def add_grade(self, subject, score):
self.grades[subject] = score
return f"{self.name} got {score} in {subject}"
def get_average(self):
if self.grades:
avg = sum(self.grades.values()) / len(self.grades)
return f"{self.name}'s average score: {avg:.1f}"
return f"{self.name} has no grades yet"
# Child class 2 - Teacher
class Teacher(Person):
"""Teacher is a person"""
def __init__(self, name, age, address, teacher_id, subject, salary):
super().__init__(name, age, address)
self.teacher_id = teacher_id
self.subject = subject
self.salary = salary
self.students = []
print(f"Teacher created: {name} (ID: {teacher_id})")
def teach(self, topic):
return f"{self.name} is teaching {topic}"
def add_student(self, student):
self.students.append(student)
return f"{student.name} added to {self.name}'s class"
def get_student_count(self):
return f"{self.name} has {len(self.students)} students"
# Child class 3 - Staff
class Staff(Person):
"""Staff is a person"""
def __init__(self, name, age, address, staff_id, department, role):
super().__init__(name, age, address)
self.staff_id = staff_id
self.department = department
self.role = role
print(f"Staff created: {name} (ID: {staff_id})")
def work(self, task):
return f"{self.name} is working on: {task}"
def get_work_hours(self):
return f"{self.name} works 40 hours per week"
# Using the system
print("=" * 50)
print("SCHOOL MANAGEMENT SYSTEM")
print("=" * 50)
print("\n=== CREATING STUDENT ===")
student = Student("Alice", 12, "123 School St", "S001", 7, ["Math", "Science"])
print(student.get_details())
print(student.celebrate_birthday())
print(student.study("Math"))
print(student.add_grade("Math", 95))
print(student.add_grade("Science", 88))
print(student.get_average())
print("\n=== CREATING TEACHER ===")
teacher = Teacher("Mr. Smith", 35, "456 Teacher Ave", "T001", "Math", 45000)
print(teacher.get_details())
print(teacher.teach("Algebra"))
print(teacher.add_student(student))
print(teacher.get_student_count())
print("\n=== CREATING STAFF ===")
staff = Staff("Ms. Johnson", 28, "789 Staff Blvd", "ST001", "Administration", "Secretary")
print(staff.get_details())
print(staff.work("Filing documents"))
print(staff.get_work_hours())
print("\n=== INHERITANCE CHAIN ===")
for cls in Student.__mro__:
print(f" {cls.__name__}")
print("\nStudent → Person → object")
print("Teacher → Person → object")
print("Staff → Person → object")
Real-world example key points:
- Person — base class with common features (name, age, address)
- Student — adds student-specific features (grade, subjects, grades)
- Teacher — adds teacher-specific features (subject, salary, students)
- Staff — adds staff-specific features (department, role)
- All share — get_details(), celebrate_birthday() from Person
Quick Check: How many child classes does the Person class have in this example? (Answer: Three — Student, Teacher, and Staff)
Best Practices for Hierarchical Inheritance
Using Hierarchical Inheritance Effectively
# Best practices for hierarchical inheritance
# 1. Keep the parent class simple and focused
# Good: Parent has only common features
class Animal:
def __init__(self, name):
self.name = name
def eat(self):
return f"{self.name} eats"
class Dog(Animal):
def bark(self):
return "Woof!"
class Cat(Animal):
def meow(self):
return "Meow!"
# Bad: Parent has too much specialized code
class Animal:
def bark(self): # Not all animals bark!
return "Bark"
def meow(self): # Not all animals meow!
return "Meow"
def fly(self): # Not all animals fly!
return "Fly"
# 2. Use meaningful names
# Good
class Vehicle:
pass
class Car(Vehicle):
pass
class Bike(Vehicle):
pass
# Bad
class A:
pass
class B(A):
pass
class C(A):
pass
# 3. Don't duplicate code across children
# Good: Common code in parent
class Shape:
def __init__(self, color):
self.color = color
class Circle(Shape):
pass
class Square(Shape):
pass
# Bad: Duplicate code in each child
class Circle:
def __init__(self, color):
self.color = color
class Square:
def __init__(self, color):
self.color = color
# 4. Use method overriding wisely
class Bird:
def fly(self):
return "Flying"
class Penguin(Bird):
def fly(self): # Penguins can't fly
return "Penguins can't fly"
# 5. Keep the hierarchy logical
# Good: Logical relationship
class Animal: pass
class Mammal(Animal): pass
class Dog(Mammal): pass
# Bad: Illogical relationship
class Car: pass
class Dog(Car): pass # A dog is not a car!
# 6. Use composition when inheritance doesn't fit
# If the relationship is "has-a" not "is-a"
class Engine:
def start(self):
return "Engine started"
class Car:
def __init__(self):
self.engine = Engine() # Car has an engine
# 7. Document your classes
class Product:
"""
Base class for all products.
Subclasses: Electronics, Clothing, Books
"""
pass
Best practices summary:
- Keep parent simple — only common, shared features in the parent
- Use meaningful names — make it clear what each class does
- Avoid duplication — don't repeat code across children
- Override wisely — only override when you need to change behavior
- Logical hierarchy — make sure the "is-a" relationship makes sense
- Consider composition — if "has-a" fits better, use composition
- Document — explain what each class is for
Quick Check: What should you do if you have the same code in multiple child classes? (Answer: Move the common code to the parent class)
Try It Yourself
Experiment with hierarchical inheritance in the editor below.
HIERARCHICAL INHERITANCE PRACTICE
========================================
1. CREATING A PARENT CLASS
Animal created: Buddy
2. CREATING CHILD CLASSES
Animal created: Whiskers
Animal created: Bessie
3. USING THE CLASSES
Buddy says Woof!
Whiskers says Meow!
Bessie says Moo!
4. UNIQUE METHODS
Buddy is fetching
Whiskers is purring
Bessie is giving milk
5. CHECKING INHERITANCE
Is Dog an Animal? True
Is Cat an Animal? True
Is Cow an Animal? True
Hierarchical inheritance practice complete!
You've Got It!
You now understand hierarchical inheritance in Python. You know how one parent class can have many children, how to share code through the parent, and how to make each child unique.
Quick Quiz
Test what you've learned:
Frequently Asked Questions
What is the difference between hierarchical and multilevel inheritance?
How many child classes can a parent have in hierarchical inheritance?
Can child classes in hierarchical inheritance have their own children?
Do all child classes need to have the same attributes?
What's a common interview question about hierarchical inheritance?
When should I use hierarchical inheritance?
Where to Go From Here
Now that you understand hierarchical inheritance, check out these related topics:
Hybrid Inheritance
Learn about combining multiple types of inheritance.
Learn More →Method Overriding
Learn more about overriding methods in child classes.
Learn More →Abstraction
Learn about hiding complex implementation details.
Learn More →