Fun with Learning Technology
LearnCoursesQuestionsTracksToolsNewsExplorePractice
Fun with Learning Technology

A new problem, explained clearly, every day.

Subscribe
Learn
  • Lessons
  • Topics
  • News
  • Tools
  • Courses
  • Career tracks
  • Everything
Site
  • About
  • Contact
  • Support
  • Privacy
  • Terms
Get the daily one

One email per new problem. No spam.

Request a tutorial

Requests shape what gets made next.

© 2026 Fun with Learning TechnologyRSS
Home›Courses›Python›Method Overriding and Polymorphism

Object-Oriented Programming

Method Overriding and Polymorphism

Method overriding allows a subclass to provide a specific implementation of a method that is already defined in its parent class. Polymorphism enables objects of different classes to be treated as instances of a common base class through a uniform interface. This architectural pattern is essential for writing extensible code that adheres to the principle of designing for interfaces rather than rigid, concrete implementations.

Understanding Method Overriding

Method overriding is the mechanism by which a subclass replaces the behavior of a parent method with its own specialized logic. When you define a method in a subclass that shares the same name and signature as a method in the parent class, Python's lookup process prioritizes the subclass version. This works because Python uses a method resolution order where it first inspects the instance's class dictionary, then ascends the inheritance tree. By redefining a method, you are effectively telling the interpreter to bypass the inherited behavior in favor of your new, more specific implementation. This is fundamental to customizing behavior in hierarchical data structures, ensuring that shared logic is reused, while distinct requirements are handled locally within the subclass. It empowers developers to maintain a consistent API across similar types while varying internal mechanics based on specific context.

class DatabaseConnection:
    def execute_query(self, query):
        # Base implementation for all connections
        print(f"Executing: {query} on base connection")

class PostgreSQLConnection(DatabaseConnection):
    def execute_query(self, query):
        # Overriding the parent method to add vendor-specific logic
        print(f"Executing {query} with PostgreSQL specific drivers")

# Usage
conn = PostgreSQLConnection()
conn.execute_query("SELECT * FROM users")

The Role of Polymorphism

Polymorphism is the concept that different objects can respond to the same message—or method call—in ways unique to their class. Because Python is dynamically typed, it cares more about whether an object has the expected method name rather than what that object technically is. When you have a collection of diverse objects that all support a shared interface, you can iterate through them and invoke the same method without knowing the specific underlying class. This creates a decouple between the caller and the implementation, allowing you to add new subclasses later without changing the code that processes these objects. The 'polymorphic' nature implies that one code path can behave differently depending on the object it receives, significantly reducing the need for complex type-checking logic such as conditional branching or explicit class verification inside your application logic.

class CloudStorage:
    def upload(self, data): print(f"Uploading {data} to Cloud")

class LocalStorage:
    def upload(self, data): print(f"Saving {data} to local disk")

# Polymorphic function: doesn't care about the object type, only the 'upload' method
def sync_data(storage_provider, data):
    storage_provider.upload(data)

sync_data(CloudStorage(), "file_a.txt")
sync_data(LocalStorage(), "file_b.txt")

Using super() to Extend Behavior

Frequently, overriding a method does not mean you want to discard the parent's logic entirely. Often, you want to perform some additional work while still relying on the foundational functionality provided by the base class. The 'super()' function is the standard tool for this; it provides a proxy object that delegates method calls to the parent class. This allows you to chain behaviors, effectively wrapping the inherited logic within your new implementation. By calling 'super().method()', you ensure that your subclass remains robust, as it respects the internal state changes or side effects defined in the hierarchy's ancestors. This pattern is essential for maintaining integrity when class members have side effects like resource initialization or logging. It enables a clean, additive approach to development where you preserve the existing contract while adding specific layers of refinement to the inherited class member.

class Logger:
    def log(self, message): print(f"LOG: {message}")

class SecureLogger(Logger):
    def log(self, message):
        # Extend behavior while reusing existing logic
        print("Checking credentials before logging...")
        super().log(message)

logger = SecureLogger()
logger.log("User logged in")

Interface Polymorphism and Duck Typing

Python's flavor of polymorphism is famously described as 'duck typing': if it walks like a duck and quacks like a duck, it is a duck. In other languages, polymorphism is often strictly tied to inheritance from an abstract base class. In Python, however, any object that implements a specific method name can participate in polymorphism, even if those objects do not share a common ancestor. This creates a flexible environment where you can design APIs based on behaviors rather than rigid type structures. You simply define a shared method name across disparate classes, and your processing functions can handle them interchangeably. This approach promotes high levels of modularity, as it allows for the integration of third-party objects that were not originally designed for your framework, provided they adhere to the interface you require for your operations.

class PDFExport:
    def export(self): print("Exporting to PDF format")

class CSVExport:
    def export(self): print("Exporting to CSV format")

# Both classes share the 'export' interface despite having no common parent
for exporter in [PDFExport(), CSVExport()]:
    exporter.export()

Design Patterns with Polymorphism

Applying polymorphism in real-world software design often leads to the strategy pattern, where an algorithm's behavior can be switched at runtime. By creating a hierarchy of objects that share the same method interface, you can pass different 'strategy' objects to a controller, allowing the application to change its logic dynamically without modifying the controller code itself. This is the pinnacle of clean architecture in Python. It avoids 'if-elif-else' chains, which are fragile and difficult to maintain as project requirements grow. By encapsulating different behaviors into distinct classes that override a common method, you enable the 'Open/Closed' principle: the code is open for extension (by adding new subclasses) but closed for modification (the core engine does not change). This makes your system significantly more resilient and easier to test, as you can substitute real objects with mock implementations during unit testing.

class DiscountStrategy:
    def calculate(self, price): return price

class SeasonalDiscount(DiscountStrategy):
    def calculate(self, price): return price * 0.9

class Checkout:
    def __init__(self, strategy=DiscountStrategy()):
        self.strategy = strategy

    def total(self, price): return self.strategy.calculate(price)

# Dynamic strategy switching
print(Checkout(SeasonalDiscount()).total(100))

Key points

  • Method overriding occurs when a subclass defines a method with the same name as its parent to provide specific functionality.
  • Python resolves method calls by looking in the instance class first before checking the parent classes.
  • Polymorphism allows different object types to be processed through a common interface.
  • The super() function is critical for calling parent class methods within a subclass to extend existing behavior.
  • Duck typing means Python relies on an object's methods rather than its inheritance hierarchy to determine compatibility.
  • Using polymorphism reduces the need for complex conditional statements that check an object's type.
  • The strategy pattern is a common design application of polymorphism for changing object behavior at runtime.
  • Adhering to a shared method interface makes your code more extensible and easier to integrate with third-party components.

Common mistakes

  • Mistake: Forgetting to call super().__init__() in a subclass constructor. Why it's wrong: The parent class's initialization logic is skipped, which can lead to missing attributes. Fix: Always call super().__init__() within the __init__ method of the child class.
  • Mistake: Misunderstanding that Python does not support explicit method overloading by signature. Why it's wrong: Defining multiple methods with the same name but different arguments will simply overwrite the previous ones. Fix: Use default arguments or *args/**kwargs to handle varied input within a single method.
  • Mistake: Trying to call a parent method using the parent's class name directly inside an instance method. Why it's wrong: It breaks the MRO (Method Resolution Order) and fails to handle multiple inheritance correctly. Fix: Use the super() function.
  • Mistake: Thinking that private attributes (prefixed with __) are truly inaccessible from subclasses. Why it's wrong: Python uses name mangling to make them harder to access, not impossible; relying on this for security is a design flaw. Fix: Use single underscores (_) to denote protected members intended for internal use.
  • Mistake: Overwriting a method but failing to maintain the expected interface (Liskov Substitution Principle). Why it's wrong: If the subclass method expects different types or raises different exceptions than the parent, code using the parent class will crash. Fix: Ensure subclass methods are compatible with the contract established by the parent class.

Interview questions

What is method overriding in Python, and how is it implemented?

Method overriding occurs when a subclass defines a method that already exists in its parent class, providing a specific implementation that replaces the parent's version. In Python, this is implemented simply by defining a method in the child class with the exact same name as the one in the parent class. When the method is called on an instance of the child class, Python automatically uses the child's version because it searches the method resolution order starting from the child class itself. This is essential for customizing behavior while maintaining a common interface.

What is the purpose of the 'super()' function during method overriding?

The 'super()' function is used to call methods from the parent class, which is critical when you want to extend the functionality of an overridden method rather than completely replacing it. By calling 'super().method_name()', you execute the logic defined in the parent class first, allowing you to build upon that result in the child class. This ensures that you do not have to duplicate code, promoting the DRY principle and ensuring that parent-level initialization or setup logic, such as that found in '__init__', is properly executed alongside the specific child requirements.

How does Python demonstrate polymorphism in practice?

Polymorphism in Python allows objects of different classes to be treated as instances of a common superclass through a unified interface. Because Python is dynamically typed, we do not need explicit type declarations. For example, if you have a function that calls a 'speak()' method on an object, it will work for any class that implements 'speak()', regardless of whether those classes share a common ancestor. This 'duck typing' philosophy means that if it walks like a duck and talks like a duck, Python treats it as a duck, allowing for flexible, interchangeable code.

Compare using composition versus using inheritance to achieve polymorphic behavior.

Inheritance establishes an 'is-a' relationship, which is ideal when you want to reuse code and enforce a specific interface through overriding methods in subclasses. Conversely, composition represents a 'has-a' relationship, where a class contains objects of other classes as attributes to delegate functionality. Composition is often preferred over deep inheritance hierarchies because it avoids tight coupling, making the codebase easier to modify and test. While inheritance directly supports polymorphism via overriding, composition achieves it through delegating method calls to internal objects, providing better encapsulation and runtime flexibility.

How does Python's Method Resolution Order (MRO) affect method overriding in multiple inheritance?

When a class inherits from multiple parents, Python uses the C3 Linearization algorithm to determine the Method Resolution Order, which is the specific sequence in which classes are searched for a method. If you override a method, the MRO dictates which implementation is executed first. You can view the MRO using the '__mro__' attribute or the 'mro()' method. Understanding this is crucial because, in complex hierarchies, unexpected overrides can lead to bugs if the developer does not know exactly which parent's implementation is being invoked when 'super()' is called during the execution.

How can you enforce an interface in Python to ensure subclasses override specific methods?

To enforce an interface, you use the 'abc' module, specifically the 'ABC' class and the '@abstractmethod' decorator. By inheriting from an abstract base class, you force any concrete subclass to implement the marked methods. If a subclass fails to override these methods, Python will raise a 'TypeError' during instantiation. This is powerful for building complex systems where you need to guarantee that all plugin modules or subclasses provide necessary functionality, ensuring that polymorphic calls throughout the rest of your application will never encounter an 'AttributeError' at runtime due to a missing implementation.

All Python interview questions →

Check yourself

1. If Class B inherits from Class A, and both define a method 'display()', what happens when you call B().display()?

  • A.Python executes both methods sequentially.
  • B.The version in Class B overrides the version in Class A.
  • C.The version in Class A is called because it is the parent.
  • D.A NameError is raised because the method is defined twice.
Show answer

B. The version in Class B overrides the version in Class A.
In Python, methods in a subclass override methods with the same name in the parent class (Method Overriding). Option 0 is wrong because they don't run together automatically. Option 2 is wrong because the subclass method takes precedence. Option 3 is wrong as it is standard behavior.

2. What is the primary benefit of using 'super()' in an overridden method?

  • A.It forces the program to execute the parent method immediately.
  • B.It allows the subclass to extend, rather than just replace, the parent's functionality.
  • C.It prevents the subclass from accessing its own attributes.
  • D.It is required to make the class compatible with multiple inheritance.
Show answer

B. It allows the subclass to extend, rather than just replace, the parent's functionality.
super() allows you to trigger the parent's implementation while adding custom logic before or after, thus extending functionality. Option 0 is imprecise. Option 2 is irrelevant. Option 3 is false, as super() is not strictly required for inheritance, only for calling parent code.

3. Given the following: class A: def show(self, x=1): pass; class B(A): def show(self, x): pass. How does this affect polymorphism?

  • A.Class B is not a valid subclass of A.
  • B.It breaks polymorphism because B changed the signature of the method.
  • C.Polymorphism remains intact as long as B can accept the same inputs A expects.
  • D.It triggers a TypeError at runtime whenever 'show' is called on B.
Show answer

C. Polymorphism remains intact as long as B can accept the same inputs A expects.
Polymorphism relies on interfaces; if B's 'show' can handle the inputs A's 'show' handles, the program remains robust. Option 0 is false. Option 1 is too strict; changing the signature is acceptable if it respects the contract. Option 3 is incorrect because a TypeError only occurs if the arguments passed don't match the required definition.

4. What does the 'Method Resolution Order' (MRO) determine in Python?

  • A.The order in which memory is allocated for class attributes.
  • B.The sequence in which Python searches for a method in a class hierarchy.
  • C.Which subclass is instantiated first when creating an object.
  • D.The priority given to methods based on their parameter count.
Show answer

B. The sequence in which Python searches for a method in a class hierarchy.
MRO defines the order of classes Python searches when looking for a method, especially in multiple inheritance. Options 0, 2, and 3 describe processes unrelated to the MRO's purpose of finding method definitions.

5. How does Python handle 'Method Overloading' (different methods with same name but different arguments)?

  • A.It requires a decorator to enable explicit overloading.
  • B.It is handled natively by the interpreter during class definition.
  • C.It does not support it; the last defined method replaces earlier ones.
  • D.It requires the use of 'abstract' classes.
Show answer

C. It does not support it; the last defined method replaces earlier ones.
Python does not have overloading based on signature; the final definition of a method name in a class scope is the one that stays. Option 0 is false. Option 1 is false. Option 3 is false as abstract classes do not change this core behavior.

Take the full Python quiz →

← PreviousInheritance and super()Next →Encapsulation and name mangling

Python

78 lessons, free to read.

All lessons →

Track your progress

Sign in to mark lessons done, score quizzes and keep notes.

Open in the app