Courseiva
PCAP-31-03Chapter 14 of 17Objective 5.2

Inheritance, Polymorphism, and Method Overriding

Inheritance, Polymorphism, and Method Overriding. These three concepts are the glue that keeps large Python programs from turning into a tangled mess of repeated code and conflicting instructions. For the PCAP-31-03 exam, you need to understand not just what they are, but how to read and write code that uses them correctly, because the exam will test your ability to predict what happens when one class borrows from another and then changes its mind.

12 min read
Intermediate
Updated Jul 23, 2026
Reviewed by Johnson Ajibi· Senior Network & Security Engineer · MSc IT Security

A simple way to picture Inheritance, Polymorphism, and Method Overriding

The Family Cookbook Analogy

Have you ever been handed a family cookbook that contains all the base recipes your grandmother perfected, and then watched as your aunt, your cousin, and your dad each took that same recipe for tomato sauce and made it their own?

Inheritance in programming works exactly like that family recipe. Your grandmother (the parent class) created a basic, reusable recipe with ingredients and steps that every family member can use. Your aunt (a child class) inherits that recipe automatically. She does not have to write down the ingredients again or re-learn how to simmer the sauce. It is already hers to use. But here is where polymorphism and method overriding come in: your cousin might take that same inherited recipe and override the 'add seasoning' step. She prefers oregano instead of basil. Your dad overrides the 'cooking time' method because he likes a thicker sauce. They both still call it 'tomato sauce' (the same method name), but the actual behaviour is different depending on who is cooking. That is polymorphism. The same instruction — 'make sauce' — produces different results based on which family member (object) executes it. The cookbook structure stays the same; only the specific steps change in certain branches of the family.

How It Actually Works

Let us start from the very beginning. In Python, a class is a blueprint for creating objects. Think of it as a cookie cutter. The objects are the cookies. Now, sometimes you want to create a new class that is very similar to an existing class but with a few extra features. Without inheritance, you would have to copy all the code from the original class and paste it into the new one. That is messy, hard to maintain, and breaks if the original class changes.

Inheritance solves this. It allows a new class (called a child class, subclass, or derived class) to automatically gain all the attributes and methods of an existing class (called a parent class, base class, or superclass). You write the common code once in the parent class. Every child class immediately gets access to it. This is sometimes expressed as an 'is-a' relationship. A Dog is a Animal. A Car is a Vehicle. A SavingsAccount is a BankAccount.

To create a child class in Python, you put the parent class name in parentheses after the child class name. For example:

class Animal: def __init__(self, name): self.name = name def speak(self): return 'Some sound'

class Dog(Animal): pass

Here, Dog inherits everything from Animal. You can create a Dog object and call speak() on it, even though Dog does not define speak itself. The method is inherited.

But what if you want the child class to behave differently? That is method overriding. You simply define a method in the child class with the exact same name as a method in the parent class. When you call that method on a child object, Python uses the child's version instead of the parent's. The parent method is replaced (overridden) for that child.

class Dog(Animal): def speak(self): return 'Woof!'

Now, Dog objects will say 'Woof!' instead of 'Some sound'. The parent's speak method still exists for other animals; it is just hidden when you use a Dog object.

This leads directly to polymorphism. Polymorphism is a Greek word meaning 'many forms'. In programming, it means that objects of different classes can respond to the same method call in their own way, as long as those classes inherit from a common parent or implement the same method interface. You can write code that treats many different types of objects uniformly, without caring exactly which class they belong to.

For example:

animals = [Dog('Fido'), Animal('Generic')] for animal in animals: print(animal.speak())

The loop calls speak() on each object. The first one is a Dog, so it prints 'Woof!'. The second is an Animal, so it prints 'Some sound'. The same line of code produces different behaviour depending on the object type. That is polymorphism in action.

Python also supports calling the parent's version of a method from within the child class. You do this using the super() function. For example, if you want a child class's speak method to do everything the parent does, plus something extra, you can write:

class PoliteDog(Animal): def speak(self): return super().speak() + ' Please!'

Here, super().speak() calls the parent Animal's speak method, gets 'Some sound', and then adds ' Please!' to it. The result is 'Some sound Please!'. This is extremely useful when you want to extend behaviour instead of completely replacing it.

The exam expects you to understand multiple inheritance as well. Python allows a class to inherit from more than one parent class. You list multiple parent classes in the parentheses, separated by commas. Python uses a specific order called the Method Resolution Order (MRO) to decide which parent's method to use if there is a conflict. The MRO follows the C3 linearization algorithm, but for the exam you mainly need to know that Python searches the child class first, then the first parent class listed, then the second parent class, and so on, moving up the hierarchy.

Why does any of this matter? Because inheritance, polymorphism, and method overriding are the foundation of code reuse and flexibility. Instead of writing the same validation logic in ten different classes, you write it once in a parent class and let every child class inherit it. If you need to change the logic later, you change it in one place. Polymorphism lets you write generic functions that work on many different types of objects, which makes your code more adaptable to change. Method overriding gives you the power to customise behaviour for specific subclasses without touching the parent code. These three concepts together form a core part of object-oriented programming (OOP), which is a major pillar of Python and a heavy focus of the PCAP-31-03 exam.

A common real Python example involves built-in types. When you define a class and implement special methods like __str__ or __len__, you are overriding methods inherited from the base object class that every Python class ultimately descends from. This is polymorphism: the built-in print() function calls the __str__ method of whatever object you give it, and different objects format themselves differently.

To summarise the key technical points:

Inheritance: a child class gains all attributes and methods from a parent class.

Method Overriding: a child class defines a method with the same name as a parent method, replacing it for that child.

Polymorphism: the same method call can behave differently on different objects, usually because those objects belong to different classes that share a common parent.

super(): a built-in function that lets you call the parent class's version of a method from within a child class.

MRO (Method Resolution Order): the order in which Python searches classes when looking for a method, important in multiple inheritance.

The PCAP-31-03 exam will test your ability to read code that involves these concepts and predict the output. You might see a question where a child class overrides a method but also calls super() inside it, and you need to know in which order the effects happen. You might be asked which class's method gets called when there is multiple inheritance. You might be asked to identify where polymorphism is being used. The key is to not just memorise definitions, but to trace the flow of execution in your head.

Remember, inheritance describes a static relationship (what a class is made of), while polymorphism describes a dynamic behaviour (what an object does at runtime). Method overriding is the mechanism that makes polymorphism possible when inheritance is involved.

This diagram shows a parent class Vehicle with three child classes, each overriding the drive() method, and a polymorphic function that calls drive() on any Vehicle object.

Walk-Through

1

Identify Common Code

Look at the classes you have and find attributes and methods that appear in multiple places. These are candidates to be moved into a parent class. For example, all user types have a username and a login method.

2

Define the Parent Class

Create a new class that contains only the shared code. This is your base class. It should represent a general concept (e.g., User) that more specific classes (AdminUser, GuestUser) will inherit from.

3

Create Child Classes with Inheritance

Define each specific class with the parent class name in parentheses: class AdminUser(User):. The child class automatically gets all attributes and methods from the parent, so you only need to write the code that is unique to AdminUser.

4

Override Methods for Custom Behaviour

If a child class needs to do something differently than the parent, write a method with the same name in the child class. This replaces the parent's version for that child. For example, AdminUser might override login() to add extra logging.

5

Use super() to Extend Parent Behaviour

Inside the overridden method, call super().method_name() to run the parent's version as well. This is important when you want to keep the parent's functionality and add more, not replace it entirely. For instance, override __init__ and then call super().__init__() to ensure parent attributes are set.

6

Leverage Polymorphism in Your Code

Write functions that accept the parent type (or any type that has the required methods). When you pass different child objects into this function, the correct overridden methods are called automatically. This makes your code easier to extend without modification.

What This Looks Like on the Job

A real IT professional working on a Python project for an e-commerce platform uses these concepts daily. Consider a team building a system that handles different types of payment methods: CreditCard, PayPal, BankTransfer, and Cryptocurrency.

Without inheritance, each payment class would repeat the same logic for validating the amount, checking a minimum threshold, and logging the transaction. This creates duplicate code that is difficult to maintain. If the logging format changes, a developer must edit every single class.

Instead, the team creates a parent class called PaymentMethod. This class contains:

A __init__ method that stores the amount and the transaction currency.

A validate method that checks the amount is positive.

A log_transaction method that writes a standardised record.

A process method that currently raises a NotImplementedError (a placeholder that forces child classes to override it).

Then they create child classes for each payment type:

CreditCard(PaymentMethod) overrides process to call a third-party credit card API, then calls super().log_transaction() to reuse the parent's logging.

PayPal(PaymentMethod) overrides process to redirect the user to a PayPal authorisation page.

Cryptocurrency(PaymentMethod) overrides both process and validate. The validate method is overridden because cryptocurrency transactions have a minimum network fee that must be checked.

Now, the team writes a single function called handle_payment(method) that accepts any PaymentMethod object. The function calls method.validate() and then method.process(). Because of polymorphism, the correct version of these methods runs automatically based on which type of object was passed in.

When a new payment type like Apple Pay is added, the team simply writes a new subclass of PaymentMethod and overrides the process method. They do not touch any existing code. This is the practical benefit: inheritance reduces duplication, and polymorphism allows the same function to handle new payment types without modification.

In an actual daily workflow, an IT professional might:

Design a parent class for common data models (e.g., a User class with username and password fields).

Create child classes like AdminUser and CustomerUser that inherit from User but override the permissions checking logic.

Write a function that takes any User object and calls a method like get_dashboard_url(), which behaves differently depending on whether the user is an admin or a customer (polymorphism).

Debug an issue where a child class accidentally shadows a parent method because the developer forgot to call super() and the parent's setup code never ran.

Use a tool like pylint or mypy to enforce that child classes override required methods.

The step-by-step process of implementing this in a real project might look like:

1.

Identify common behaviour across similar classes.

2.

Create a parent (base) class that contains that common behaviour.

3.

Identify behaviour that differs — these become methods to override in child classes.

4.

Write child classes that inherit from the parent and override only the methods that need customisation.

5.

Write your main application logic to depend on the parent type, so it works with any child class polymorphically.

6.

If a child class needs to extend parent behaviour, use super() to call the parent's method before or after adding custom logic.

This approach is standard in frameworks like Django (where you inherit from models.Model and override save() or delete()) and in testing frameworks (where you inherit from unittest.TestCase and override setUp()). It is not an abstract exam concept; it is how professional Python code is structured every day.

How PCAP-31-03 Actually Tests This

PCAP-31-03 exam objective 5.2 specifically tests your ability to 'apply inheritance, call parent class methods, and use polymorphic behaviour'. The exam is not asking you to write a big program; it asks you to read short code snippets (usually 10-20 lines) and predict what will be printed or which statement is true.

Here is exactly what you need to know for the exam:

The syntax for creating a child class: class Child(Parent):.

How to call the parent's __init__ using super().__init__() from within the child's __init__. The exam loves this pattern.

Method overriding works by simply defining a method with the same name in the child class. The child version shadows the parent version for objects of the child class.

The super() function can be used to call any inherited method, not just __init__. For example, super().some_method().

In multiple inheritance, Python's Method Resolution Order (MRO) is determined by the class hierarchy. You can inspect the MRO by using ClassName.__mro__ or ClassName.mro(). The exam may provide a diamond inheritance pattern (A -> B -> D and A -> C -> D) and ask which method gets called. The answer follows the C3 linearization order: child first, then left-to-right parent classes.

Polymorphism in the exam context usually means that you can have a list containing objects of different types that all inherit from a common parent, and calling the same method on each will produce different results.

The exam might test whether you understand that Python does not enforce method overriding at compile time. There is no 'override' keyword like in some other languages. You just define a method with the same name and it overrides. This means accidental overriding is possible if you mistype a method name.

They may also test isinstance() and issubclass() built-in functions. isinstance(object, ClassName) returns True if the object is an instance of that class or any of its subclasses. issubclass(ChildClass, ParentClass) returns True if ChildClass is a subclass of ParentClass.

Common traps the exam sets:

They define a child class that overrides a method but forgets to call super() in the __init__. The parent's __init__ never runs, so instance variables from the parent are missing. The code might raise an AttributeError.

They use multiple inheritance and two parent classes both define a method with the same name. The exam asks which version is called. You must trace the MRO.

They create a method in the child class with the same name as a parent method but with a different number of parameters. In Python, this is still overriding because Python does not support method overloading by signature. The child method replaces the parent method entirely, even if the number of parameters is different.

They ask whether polymorphism requires inheritance. In Python, it does not strictly require it because of duck typing (if it walks like a duck and quacks like a duck, it is a duck). But for the exam, assume that polymorphic behaviour through inheritance is the main focus.

They present a code snippet with a for loop that calls a method on objects of different subclasses, and you must identify that the method called is the one defined in each specific subclass, not the parent.

Key definitions to memorise for the exam:

super(): Returns a proxy object that delegates method calls to a parent or sibling class, following the MRO.

Method Resolution Order (MRO): The order in which Python looks for a method in a hierarchy of classes.

issubclass(classA, classB): Returns True if classA is a subclass of classB.

isinstance(obj, classB): Returns True if obj is an instance of classB or any subclass of classB.

Override: To replace an inherited method by defining a method with the same name in the child class.

Polymorphism: The ability of objects of different types to respond to the same method call in their own way.

The exam typically has 2-4 questions on this topic. They are usually multiple choice or single-select. You will see code snippets and must choose the correct output, or determine whether a statement about the code is true or false. Practice tracing small inheritance hierarchies on paper. Know exactly what happens when a child class does not define __init__ (the parent's __init__ is called automatically) versus when it does define __init__ but does not call super().__init__() (the parent's __init__ is not called).

Key Takeaways

A child class inherits all attributes and methods from its parent class, including the __init__ method.

Method overriding means redefining a method in the child class with the exact same name as a method in the parent class.

The super() function is used to call the parent class's version of a method from within the child class, essential for extending behaviour.

Polymorphism allows different object types to respond to the same method call, which lets you write flexible code that works on a family of classes.

In multiple inheritance, Python uses the Method Resolution Order (MRO), which is determined by the C3 linearization algorithm and can be inspected with ClassName.__mro__.

If a child class defines an __init__ method and does not call super().__init__(), the parent class's __init__ is not executed automatically.

The issubclass() function checks class relationships, while isinstance() checks object relationships, and both respect the full inheritance chain.

Easy to Mix Up

These come up on the exam all the time. Here's how to tell them apart.

Method Overriding

Occurs in a child class redefining a parent method

Same method name, same number of parameters typically

Python supports this natively

Method Overloading

Occurs in the same class with different method signatures

Same method name, different number or type of parameters

Python does not support method overloading; using same name replaces the earlier definition

Inheritance

Creates an 'is-a' relationship (e.g., a Dog is an Animal)

Child class gains all parent attributes and methods automatically

Tight coupling between child and parent classes

Composition

Creates a 'has-a' relationship (e.g., a Car has an Engine)

Class contains instances of other classes, delegates work to them

Looser coupling, more flexible but requires explicit delegation

super()

Works with multiple inheritance and MRO correctly

Syntax: super().method_name()

Preferred way to call parent methods in complex hierarchies

Direct Parent Class Call

Only calls the specific named parent class directly

Syntax: ParentClass.method_name(self, ...)

Can break with multiple inheritance if you hardcode a specific parent

Polymorphism via Inheritance

Objects must share a common parent class

Strongly tied to the class hierarchy

More explicit and safer for large projects

Polymorphism via Duck Typing

Objects only need to have the required method; no class relationship needed

Relies on the 'if it walks like a duck' principle

More flexible but can lead to runtime errors if method is missing

Watch Out for These

Mistake

If a child class defines a method with the same name as a parent method, Python calls both methods automatically.

Correct

The child method completely overrides the parent method. The parent method is not called unless you explicitly use super() to call it.

This misunderstanding comes from thinking of overriding as 'adding onto' rather than 'replacing'. In many non-technical contexts, if you override a rule, you replace it entirely.

Mistake

Polymorphism only works if I explicitly declare an interface or abstract class, like in Java.

Correct

Python uses duck typing. If an object has the required method, polymorphism works without any formal declaration of inheritance. However, for the PCAP exam, inheritance-based polymorphism is the primary focus.

Beginners often transfer knowledge from statically typed languages. Python's dynamic nature is a surprise to them.

Mistake

Calling super().__init__() is optional and only needed if I want to access the parent's attributes.

Correct

If the parent's __init__ sets up important attributes (like self.name), and you do not call super().__init__(), those attributes will not exist on the child object, leading to AttributeError.

New learners often think __init__ is just for show, not realising it is a method that must be executed to initialise instance variables.

Mistake

Multiple inheritance in Python works like a simple tree; the child gets methods from both parents equally with no conflict.

Correct

Multiple inheritance follows a strict left-to-right depth-first MRO. If both parents define a method with the same name, only the one from the first parent listed is used (unless the child overrides it).

People intuitively think 'both' means 'both get used', but Python resolves conflicts by a specific order to avoid ambiguity.

Mistake

If I override a method in a child class, the parent class's version of that method is deleted or gone forever.

Correct

The parent's method still exists in the parent class. Any object of the parent class will still use the parent's version. The child's objects simply use the child's version instead.

The word 'override' sounds destructive. Learners forget that the parent class remains untouched.

Mistake

You can only use super() inside the __init__ method.

Correct

super() can be used inside any method of the child class to call any method from the parent class.

Many tutorial examples only show super() in __init__, so beginners assume it is restricted to that context.

Do You Actually Know This?

Reveal each answer, then mark whether you got it right. Score 60%+ to unlock the next chapter.

Frequently Asked Questions

What is the difference between inheritance and polymorphism?

Inheritance is a mechanism where a class gets attributes and methods from a parent class. Polymorphism is the ability to use objects of different types through a common interface, often enabled by inheritance.

Do I always need to use super() in the child class __init__?

If the parent class's __init__ sets up important attributes that the child needs, yes, you must call super().__init__(). If the child's __init__ completely replaces all setup, or if the parent has no __init__, you do not technically need it, but it is best practice to call it.

Can a child class inherit from more than one parent class?

Yes, Python supports multiple inheritance. You list multiple parent classes in the class definition: class Child(Parent1, Parent2):. The order matters for method resolution.

What happens if a child class does not define __init__?

If a child class does not define __init__, Python automatically calls the parent class's __init__ when you create an instance of the child class. The child object will be initialised exactly like a parent object.

How does Python decide which method to call in multiple inheritance?

Python uses the Method Resolution Order (MRO), which follows the C3 linearization algorithm. It searches the child class first, then the first parent class and its ancestors, then the second parent class and its ancestors, and so on. You can view the MRO with MyClass.__mro__.

Is polymorphism only possible through inheritance?

In Python, polymorphism also works through duck typing: if an object has the method that is being called, it will work, even if there is no inheritance relationship. However, the PCAP exam focuses on inheritance-based polymorphism.

Terms Worth Knowing

Keep going

You've finished Inheritance, Polymorphism, and Method Overriding. Continue through the PCAP-31-03 study guide to build a complete picture of the exam.

Done with this chapter?