Object-Oriented Programming is powerful because it lets us build flexible, reusable systems. Two core principles that unlock this power are:
- ✅ Inheritance – Reuse and extend existing code
- ✅ Polymorphism – Use the same interface for different behaviors
If you’ve ever written the same logic twice with minor changes… or wished to write generic code that can adapt to many types these concepts are for you.
In this guide, you’ll learn:
- What inheritance and polymorphism mean (in plain English)
- How to use them in Python with clear examples
- When to use
super()
and how to override methods - Real-world analogies and gotchas to avoid
- Best practices and code patterns that scale
Let’s go step by step.
🧬 What is Inheritance?
Inheritance allows one class (called the child or subclass) to inherit properties and behavior from another class (called the parent or superclass).
It promotes code reuse and helps you follow the DRY principle: Don’t Repeat Yourself.
📦 Real-Life Analogy
Imagine a
Vehicle
class. ACar
and aBike
are both vehicles. Instead of rewriting everything,Car
andBike
can inherit fromVehicle
.
🧱 Defining a Parent Class
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
print(f"{self.name} makes a sound.")
This is a base class. Now let’s extend it.
🐶 Creating a Child Class
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # Reuse Animal’s __init__
self.breed = breed
def speak(self):
print(f"{self.name} barks.")
✅ Breakdown:
Dog(Animal)
meansDog
inherits fromAnimal
super().__init__(name)
calls the parent constructor- You can add new attributes (like
breed
) - You can also override methods (like
speak()
)
🔁 What is Method Overriding?
If the child class defines a method with the same name as one in the parent class, Python uses the child’s version.
a = Animal("Leo")
a.speak() # Leo makes a sound.
d = Dog("Bruno", "Labrador")
d.speak() # Bruno barks.
✅ This is method overriding in action.
🧠 Why Use super()
?
super()
lets you:
- Reuse logic from the parent class
- Avoid hardcoding the parent name
- Work well with multiple inheritance
It looks like:
super().__init__(args)
🧬 What is Polymorphism?
Polymorphism means “many forms.”
In OOP, it lets different classes implement the same method name, and you can call it without knowing the exact class type.
🧪 Example:
class Cat(Animal):
def speak(self):
print(f"{self.name} meows.")
animals = [Dog("Bruno", "Labrador"), Cat("Whiskers")]
for animal in animals:
animal.speak()
Output:
Bruno barks.
Whiskers meows.
✅ Even though Dog
and Cat
behave differently, we call speak()
the same way.
This is polymorphism and it makes your code flexible.
🧱 Real-World Example: Payment Systems
class PaymentProcessor:
def process(self, amount):
raise NotImplementedError("Must implement in subclass")
class PayPalProcessor(PaymentProcessor):
def process(self, amount):
print(f"Processing ₹{amount} via PayPal")
class StripeProcessor(PaymentProcessor):
def process(self, amount):
print(f"Processing ₹{amount} via Stripe")
Now:
def checkout(processor: PaymentProcessor, amount):
processor.process(amount)
checkout(PayPalProcessor(), 100)
checkout(StripeProcessor(), 200)
✅ This design lets us plug in any processor, without rewriting the checkout logic.
⚠️ Inheritance vs Composition
Inheritance is powerful, but don’t overuse it. If a class “has a” relationship (not “is a”), prefer composition.
E.g., a
Car
has aGPS
(composition)
ACar
is aVehicle
(inheritance)
💡 Best Practices
Rule | Why |
---|---|
Use inheritance only when it makes sense (“is-a” relationship) | Avoid tight coupling |
Always use super() in child constructors | Keeps the parent logic intact |
Override only what needs changing | Avoid code duplication |
Don’t override just to pass | Use pass or default to parent |
Prefer composition over deep inheritance | Easier to test and maintain |