Inheritance & Method Overriding
Extend base class capabilities, leverage super(), and implement polymorphic interfaces across subclasses.
Subclasses Specialize, super() Extends
When a subclass defines a method with the same name as its parent, calling that method on a subclass instance runs the subclass's version — this is method overriding. It lets each subclass specialize shared behavior without touching the parent.
super() is not a hardcoded reference to "the parent class" — it's a dynamic proxy that dispatches to the next class in the runtime Method Resolution Order (MRO), which Python computes with an algorithm called C3 Linearization.
self.salary = salary # name never set!
super().__init__(name)
self.salary = salary
Inheritance is like a franchise inheriting the parent company's playbook: each location (subclass) can override a specific procedure (method) for local needs, but calling super() is like phoning corporate headquarters to run their standard process first, then adding your own steps on top.
Specializing Behavior via Method Overriding
Run the snippet below to see two subclasses override the same parent method differently:
class NotificationSender:
def send(self, recipient, message):
return f"[GENERIC] To: {recipient} | Msg: {message}"
class EmailSender(NotificationSender):
def send(self, recipient, message):
# Overriding base method with email-specific formatting
return f"[EMAIL SMTP] Sending '{message}' to <{recipient}>"
class SMSSender(NotificationSender):
def send(self, recipient, message):
# Overriding with SMS-specific formatting
return f"[SMS GATEWAY] Sending '{message}' to phone {recipient}"
services = [
EmailSender(),
SMSSender(),
NotificationSender()
]
for s in services:
print(s.send("user@pylabs.dev", "Welcome aboard!"))A subclass redefining a method with the same name as the parent's replaces that behavior for its instances.
A dynamic proxy to the next class in the MRO — not a hardcoded reference to the immediate parent.
The linear class order Python computed via C3 Linearization for attribute and method lookup.
Confirms an object shares ancestry with a class, enabling code that works across an entire subtype tree.
Step Through Overriding, super(), and the MRO
Click through the three concepts that together make inheritance work correctly:
A subclass defines a method with the same name as the parent's — calling it on the subclass instance runs this version instead of the parent's.
Subtype Polymorphism vs Duck Typing
Both let one function handle many types — knowing which one you're relying on avoids surprises.
Subtype polymorphism
isinstance(obj, StorageEngine)True for the base class and any subclass — guarantees a shared ancestry and interface.
issubclass(MemoryStorage, StorageEngine)Checks the class relationship directly, without needing an instance.
Duck typing
if hasattr(obj, 'save'): obj.save()Prioritizes behavior over ancestry — any object with the right method works, related or not.
Method resolution
ClassName.__mro__The exact linear order Python searches for attributes and methods across the inheritance tree.
Worked Hands-on Walkthroughs
Progress step-by-step from overriding through super()-based extension to polymorphic introspection:
Specializing Behavior via Method Overriding
Each subclass overrides send() with its own transport formatting while sharing the common NotificationSender interface.
class NotificationSender:
def send(self, recipient, message):
return f"[GENERIC] To: {recipient} | Msg: {message}"
class EmailSender(NotificationSender):
def send(self, recipient, message):
# Overriding base method with email-specific formatting
return f"[EMAIL SMTP] Sending '{message}' to <{recipient}>"
class SMSSender(NotificationSender):
def send(self, recipient, message):
# Overriding with SMS-specific formatting
return f"[SMS GATEWAY] Sending '{message}' to phone {recipient}"
services = [
EmailSender(),
SMSSender(),
NotificationSender()
]
for s in services:
print(s.send("user@pylabs.dev", "Welcome aboard!"))Each subclass overrides send() with its own transport formatting while sharing the common NotificationSender interface.
Extending Parent State Using super().__init__
super().__init__ sets emp_id and base_salary on the Employee base, and super().calculate_pay() computes base earnings.
class Employee:
def __init__(self, emp_id, name, base_salary):
self.emp_id = emp_id
self.name = name
self.base_salary = base_salary
def calculate_pay(self):
return self.base_salary
class Manager(Employee):
def __init__(self, emp_id, name, base_salary, bonus):
# 1. Delegate core initialization to Employee superclass
super().__init__(emp_id, name, base_salary)
# 2. Add Manager-specific state
self.bonus = bonus
def calculate_pay(self):
# 3. Extend base pay calculation
return super().calculate_pay() + self.bonus
mgr = Manager(101, "Elena Rostova", 95000, 15000)
print(f"{mgr.name} (ID #{mgr.emp_id}) total pay: ${mgr.calculate_pay()}")super().__init__ sets emp_id and base_salary on the Employee base, and super().calculate_pay() computes base earnings.
Polymorphism and Introspection with isinstance & issubclass
persist_record handles any StorageEngine subtype polymorphically, and isinstance() checks support tuple alternatives.
class StorageEngine:
def save(self, key, data): raise NotImplementedError
class MemoryStorage(StorageEngine):
def __init__(self): self.store = {}
def save(self, key, data): self.store[key] = data
class CloudStorage(StorageEngine):
def save(self, key, data): print(f"Uploading {key} to S3/R2")
def persist_record(engine, key, data):
# Verify nominal subtype polymorphism
if not isinstance(engine, StorageEngine):
raise TypeError(f"Expected StorageEngine, got {type(engine).__name__}")
engine.save(key, data)
mem = MemoryStorage()
persist_record(mem, "user:1", {"name": "Kaelen"})
print("Memory store contents:", mem.store)
print("Is MemoryStorage subclass of StorageEngine?", issubclass(MemoryStorage, StorageEngine))
print("Is mem an instance of (CloudStorage, MemoryStorage)?", isinstance(mem, (CloudStorage, MemoryStorage)))persist_record handles any StorageEngine subtype polymorphically, and isinstance() checks support tuple alternatives.
Common Traps & How to Avoid Them
Study the mistakes and clean fixes below before building your own class hierarchies:
# Mistyped variable or missing closing parenthesis
result = inheritance___polymorphism_val# Declare and verify variables before execution
val = 10
print("Defined correctly:", val)Beginners frequently encounter typos or forgotten definitions before using an identifier.
# Modifying shared state or unexpected variable shadowing
def update_state(data=[]):
data.append(1)
return datadef update_state(data=None):
if data is None:
data = []
data.append(1)
return dataDefault arguments in Python are evaluated once at function definition time, not at invocation time.
# Opening resources in a tight loop without closing or bounding memory
files = [open(f"log_{i}.txt", "w") for i in range(5000)]for i in range(5):
with open(f"log_{i}.txt", "w") as f:
f.write("Safe")Failing to release system resources leads to process termination under heavy production workloads.
Real-World Systems in Action
See how scripts, backend services, and enterprise codebases build on inheritance and polymorphism:
Local Developer Automation Tool
Individual developers and IT administrators automate everyday repetitive workflows.
A clean standalone script providing fast, local automation with zero dependencies.
class Vehicle:
def move(self):
return "Moving forward"
class Airplane(Vehicle):
def move(self):
return "Flying through the sky"
plane = Airplane()
print("Plane movement:", plane.move())Backend API Service Integration
SaaS platforms run backend microservices processing requests concurrently.
A modular service handler integrating validation and structured responses.
# Service integration layer
def handle_request(payload: dict) -> dict:
return {"status": "success", "processed": True}High-Throughput Cloud Architecture
GUI toolkits and HTTP client middleware use polymorphism so callers treat all request handlers identically regardless of subtype.
GUI toolkits and HTTP client middleware use polymorphism so callers treat all request handlers identically regardless of subtype.
# High-resilience production pattern
import time
def process_stream(events: list) -> int:
processed = 0
for event in events:
if event:
processed += 1
return processedKey Points & Concept Review
- Method overriding allows subclasses to tailor parent behavior to specialized requirements.
- super() is a dynamic proxy that dispatches method calls to the next class in the runtime MRO.
- Always use super().__init__() in subclasses to ensure base class state and invariants are initialized.
- Python resolves multiple inheritance using the C3 Linearization algorithm accessible via __mro__.
- Duck typing prioritizes object behavior over nominal class pedigree, while subtype polymorphism guarantees shared ancestry.
- isinstance(obj, tuple) and issubclass(cls, tuple) provide runtime validation across inheritance trees.
Frequently Asked Questions & Reference Hub
super() feels abstract until you actually print ClassName.__mro__ for a hierarchy you wrote yourself — it's completely fine if multiple inheritance order confuses you at first; most working Python developers rarely go past two or three levels deep.
See how subclasses replace parent behavior.
Understand what super() actually dispatches to.
Learn the algorithm behind __mro__.
See the difference between checking instances and classes.
Compare behavior-based and ancestry-based polymorphism.
Explore how Python resolves the classic diamond inheritance case.
You've Mastered Inheritance & Polymorphism!
You understand overriding, super(), the MRO, and polymorphic code. Now lock in your knowledge with hands-on practice.
Build a small subclass hierarchy that overrides and extends via super().
Earn experience points toward your developer rank as soon as you pass.
Bite-sized knowledge check with instant explanations for every question.
Worked example
Every vehicle honks, but buses charge fare. Bus inherits Vehicle and adds its own twist — code reuse plus specialisation.
class Vehicle:
def describe(self):
return "A vehicle"
class Bus(Vehicle):
def fare(self, km):
return km * 2.5
bus = Bus()
print(bus.describe(), bus.fare(10))
How it works, step by step
- class Bus(Vehicle) means Bus gets everything Vehicle has.
- describe() was never written twice — inheritance supplied it.
- fare() exists only on Bus: specialisation.
Inheritance & Method Overriding practice
Create a parent class Vehicle with a move method that prints Moving. Create a child class Bike that overrides it to print Pedaling. Create a Bike and call move().
Coding Practice is Locked
Sign in to access the interactive Python editor, write and run code checks against automated tests, use hints, and earn XP.
Practice quiz
Check yourself
15 questions with explanations after submission, so you can correct course as you go.
Inheritance & Method Overriding quiz
LockedYou must be signed in to take this quiz. Sign in to test your understanding, track your answers, and earn XP.
Lesson incomplete
Not completedCreate an ID or sign in to save lesson completion across devices.