Polymorphism in Python - Interview Questions and Answers

Polymorphism allows objects of different classes to be treated as objects of a common superclass. It enables method overriding and dynamic method invocation.

Method overriding occurs when a subclass provides a specific implementation of a method already defined in its superclass.

Method overriding is implemented by defining a method in a subclass with the same name as a method in the parent class.

Dynamic method invocation means calling a method at runtime based on the actual object type, enabling polymorphism.

  • Method Overloading: Same method name but different parameters (not supported in Python).
  • Method Overriding: Subclass method replaces superclass method with the same signature.

Yes, a subclass can override multiple methods inherited from its parent class.

Yes, using super().method_name().

class Parent:
    def show(self):
        print("Parent class method")

class Child(Parent):
    def show(self):
        print("Child class method")
        super().show()

obj = Child()
obj.show()

Output

Child class method  
Parent class method  

It will not override the method; instead, the subclass will have an additional method.

 

Yes, otherwise, Python will treat it as a new method instead of overriding it.

No, private methods (prefix __) are name-mangled and cannot be overridden in a subclass.

It refers to selecting and invoking a method at runtime based on the object type, allowing polymorphic behavior.

Python achieves dynamic method invocation through method overriding and calling methods dynamically at runtime.

It helps determine an object's class before calling a method to ensure compatibility.

def process(obj):
    if isinstance(obj, Parent):
        obj.show()

process(Child())  # Calls overridden method

 

Yes, using getattr(object, "method_name")().

class Demo:
    def greet(self):
        print("Hello!")

obj = Demo()
getattr(obj, "greet")()  # Output: Hello!

 

super() dynamically resolves the next method in the method resolution order (MRO), ensuring overridden methods are correctly called.

It determines the order in which methods are inherited and overridden in multiple inheritance scenarios.

Using ClassName.__mro__ or ClassName.mro().

print(Child.mro())  # Displays method resolution order

 

The child's __init__ overrides the parent's, but super().__init__() can be used to call the parent?s initializer.

Yes, but it should be used cautiously in multiple inheritance to avoid unexpected behavior.

Python follows the C3 linearization (MRO) to determine method resolution order.

The method from the parent class is used.

By using the final method concept (not natively in Python, but can be simulated using @final from typing).

from typing import final

class Parent:
    @final
    def show(self):
        print("Cannot be overridden")

class Child(Parent):
    pass  # show() cannot be overridden

 

Yes, but it is generally used within overridden methods.

class A:
    def show(self):
        print("A")

class B(A):
    def show(self):
        print("B")
        super().show()

class C(B):
    def show(self):
        print("C")
        super().show()

obj = C()
obj.show()

Output

C  
B  
A  

The method is inherited and can be used directly.

It allows different object types to be treated uniformly while invoking their specific implementations at runtime.

  • super().method() dynamically resolves method calls in multiple inheritance.
  • Parent.method(self) directly calls the parent?s method, bypassing MRO.

It raises TypeError since there is no superclass to call.

Yes, but it?s not truly method overriding because static methods do not rely on self.

class Parent:
    @staticmethod
    def greet():
        print("Hello from Parent")

class Child(Parent):
    @staticmethod
    def greet():
        print("Hello from Child")

Child.greet()  # Output: Hello from Child

 

Yes, using @classmethod.

class Parent:
    @classmethod
    def show(cls):
        print("Parent class method")

class Child(Parent):
    @classmethod
    def show(cls):
        print("Child class method")

Child.show()

 

By defining __str__ in a subclass, we can customize how an object is represented as a string.

class Parent:
    def __str__(self):
        return "This is the Parent class"

class Child(Parent):
    def __str__(self):
        return "This is the Child class"

obj = Child()
print(obj)  # Output: This is the Child class

 

The __eq__ method can be overridden to compare objects based on attributes instead of memory locations.

class Person:
    def __init__(self, name):
        self.name = name
    
    def __eq__(self, other):
        return self.name == other.name

p1 = Person("Alice")
p2 = Person("Alice")
print(p1 == p2)  # Output: True

 

By overriding __lt__, we can define custom behavior for < comparisons.

class Box:
    def __init__(self, weight):
        self.weight = weight
    
    def __lt__(self, other):
        return self.weight < other.weight

b1 = Box(10)
b2 = Box(15)
print(b1 < b2)  # Output: True

 

Python follows the Method Resolution Order (MRO) to determine which method to call.

class A:
    def show(self):
        print("A")

class B(A):
    def show(self):
        print("B")

class C(B):
    pass

obj = C()
obj.show()  # Output: B (inherits from B)

 

Yes, overriding __call__ allows an instance to be called like a function.

class Demo:
    def __call__(self, x):
        return x * 2

obj = Demo()
print(obj(5))  # Output: 10

 

We can use decorators like @classmethod, @staticmethod, or custom decorators to modify method behavior.

def uppercase_decorator(func):
    def wrapper():
        return func().upper()
    return wrapper

class Greet:
    @uppercase_decorator
    def message(self):
        return "hello"

obj = Greet()
print(obj.message())  # Output: HELLO

 

We can assign a new function to an instance method dynamically.

class Animal:
    def speak(self):
        print("Animal speaks")

def bark():
    print("Dog barks")

obj = Animal()
obj.speak = bark  # Overriding at runtime
obj.speak()  # Output: Dog barks

 

APIs use method overriding in subclassing to allow developers to customize behavior without modifying the base class.

from flask import Flask

class CustomFlask(Flask):
    def run(self, *args, **kwargs):
        print("Custom server start")
        super().run(*args, **kwargs)

app = CustomFlask(__name__)

 

Yes, we can override properties to add validation logic.

class Person:
    def __init__(self, age):
        self._age = age
    
    @property
    def age(self):
        return self._age
    
    @age.setter
    def age(self, value):
        if value < 0:
            raise ValueError("Age cannot be negative")
        self._age = value

p = Person(30)
p.age = -5  # Raises ValueError

 

Abstract base classes (ABCs) enforce method overriding in subclasses.

from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def speak(self):
        pass

class Dog(Animal):
    def speak(self):
        print("Bark")

obj = Dog()
obj.speak()  # Output: Bark

 

In a singleton, overridden methods apply to the single instance globally.

Mixins provide additional functionality that subclasses can override as needed.

class LogMixin:
    def log(self, message):
        print(f"Log: {message}")

class App(LogMixin):
    def log(self, message):
        print(f"App log: {message}")

a = App()
a.log("Starting...")  # Output: App log: Starting...

 

Using super() ensures all inherited methods are properly called in MRO order.

Django models override methods like save() and get_absolute_url() to customize database behavior.

class User(models.Model):
    def save(self, *args, **kwargs):
        print("Custom save method")
        super().save(*args, **kwargs)

By overriding methods in child classes, we can inject different dependencies dynamically.

Overridden methods slightly increase method lookup time due to dynamic resolution.

Metaclasses allow method overriding at the class creation level.

class Meta(type):
    def __new__(cls, name, bases, dct):
        dct["greet"] = lambda self: print("Hello from metaclass")
        return super().__new__(cls, name, bases, dct)

class Demo(metaclass=Meta):
    pass

obj = Demo()
obj.greet()  # Output: Hello from metaclass

Yes, using @final prevents subclasses from overriding a method.

from typing import final

class Parent:
    @final
    def show(self):
        print("Cannot be overridden")

Overriding __getitem__ enables indexing behavior.

class MyList:
    def __init__(self, items):
        self.items = items
    
    def __getitem__(self, index):
        return self.items[index] * 2

lst = MyList([1, 2, 3])
print(lst[1])  # Output: 4

It follows the C3 linearization (MRO) to determine which method to invoke.

Share   Share