Inheritance & Polymorphism — Python Questions with Answers
Start with the study notes on Inheritance & Polymorphism (definitions, examples and a quick revision), then work through 30 practice questions, each with the correct answer and a clear explanation of why it is right. When you are ready, take this topic as an interactive quiz to test yourself.
7 easy · 16 medium · 7 hard · Part of Python: Master Python from basics to advanced - 20 topics.
Topic overview
Inheritance and Polymorphism – building on other classes
1. Inheritance
Inheritance lets a child class automatically get the attributes and methods of a parent class. Write the shared code once, in the parent.
class Animal:
def eat(self):
print("eating")
class Dog(Animal): # Dog inherits from Animal
def fetch(self):
print("fetching")
d = Dog()
d.eat() # inherited
d.fetch() # Dog's own method
Outputeating
fetching
Example: Inheritance means “is-a”: a Dog is an Animal, a Car is a Vehicle.
2. Overriding methods
A child can override a parent's method by defining one with the same name. Python looks in the object's own class first, then climbs up to the parent.
class Animal:
def speak(self):
return "some sound"
class Cat(Animal):
def speak(self):
return "Meow"
print(Cat().speak())
print(Animal().speak()) # the parent is unchanged
OutputMeow
some sound
3. super()
super() calls the parent's version of a method. Its most common use is running the parent's __init__, because overriding __init__ means the parent's one no longer runs by itself.
class Animal:
def __init__(self, name):
self.name = name
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # let the parent set name
self.breed = breed
d = Dog("Rex", "Labrador")
print(d.name, d.breed)
OutputRex Labrador
Remember: If you forget super().__init__(name), self.name is never set, and reading it gives AttributeError.
Extending instead of replacing
class A:
def greet(self):
return "hi from A"
class B(A):
def greet(self):
return super().greet() + " and B"
print(B().greet())
Outputhi from A and B
4. isinstance() and issubclass()
class Animal: pass
class Dog(Animal): pass
d = Dog()
print(isinstance(d, Dog), isinstance(d, Animal))
print(type(d) == Animal) # exact type only
print(issubclass(Dog, Animal), issubclass(bool, int))
OutputTrue True
False
True True
Remember: Prefer isinstance(): it accepts subclasses, while type(x) == Dog rejects them.
5. Polymorphism
Polymorphism means different classes answering the same method call in their own way. The calling code doesn't need to know which class it has.
class Dog:
def speak(self): return "Woof"
class Cat:
def speak(self): return "Meow"
class Robot:
def speak(self): return "Beep"
for thing in [Dog(), Cat(), Robot()]:
print(thing.speak())
OutputWoof
Meow
Beep
Duck typing
“If it walks like a duck and quacks like a duck…”: Python cares what an object can do, not what class it comes from. The Robot above works without any shared parent class.
6. Composition: “has-a”
Composition means storing another object as an attribute. Use it when the relationship is “has-a” rather than “is-a”.
class Engine:
def start(self):
return "vroom"
class Car:
def __init__(self):
self.engine = Engine() # a Car HAS an Engine
def start(self):
return self.engine.start()
print(Car().start())
Outputvroom
Remember: Common advice: prefer composition over inheritance unless “is-a” clearly fits.
7. Multiple inheritance and the MRO
A class can have several parents. When they share a method name, Python follows the Method Resolution Order (MRO), checking parents left to right.
class A:
def hello(self): return "A"
class B:
def hello(self): return "B"
class C(A, B):
pass
print(C().hello())
print([k.__name__ for k in C.__mro__])
OutputA
['C', 'A', 'B', 'object']
Example: Every class ultimately inherits from object.
8. Methods children must write
A parent can force children to provide a method: raise NotImplementedError, or use the abc module's @abstractmethod.
class Shape:
def area(self):
raise NotImplementedError("subclass must define area")
class Square(Shape):
def __init__(self, side):
self.side = side
def area(self):
return self.side ** 2
print(Square(3).area())
Output9
9. Common misconceptions
Misconception
Correct understanding
The parent's __init__ runs automatically even if the child defines its own.
Only the child's lookup changes; the parent keeps its version.
Polymorphism needs a shared parent class.
Python uses duck typing; any object with the method works.
A Car should inherit from Engine.
A Car has an Engine; use composition.
type(x) == Parent is the best check.
isinstance(x, Parent) also accepts subclasses.
10. Quick revision
Inheritance
A child class gets a parent's methods.
Override
Redefine a parent's method in the child.
super()
Calls the parent's version of a method.
Polymorphism
Same method call, different behaviour.
Duck typing
What an object can do matters, not its class.
Composition
“Has-a”: an object stored as an attribute.
MRO
The order Python searches classes for a method.
object
The root of every class.
11. Test your understanding
How do you make Dog inherit from Animal?
Show answer
class Dog(Animal):
Both classes define speak(). Which runs for Dog().speak()?
Show answer
Dog's version (it overrides the parent's).
What is super() most often used for?
Show answer
Calling the parent's __init__.
d = Dog(), Dog inherits Animal. What is isinstance(d, Animal)?
Show answer
True
What is polymorphism?
Show answer
Different classes responding to the same method call in their own way.
What is duck typing?
Show answer
Caring about what an object can do, not what class it is.
Should Car inherit from Engine?
Show answer
No: a Car has an Engine, so use composition.
class C(A, B): both define hello(). Which runs?
Show answer
A's, because the MRO checks parents left to right.
Key takeaway: Inherit for “is-a” relationships, override to specialise, call super() to keep the parent's work, and let polymorphism treat different objects the same way.
Notes written by Velnov. Every code example with an output was run in Python 3 to check that the output shown is exactly what Python prints.
class Animal:
def eat(self):
print("eating")
class Dog(Animal): # Dog inherits eat()
pass
Dog().eat() # eating
A A class (child) automatically gets the attributes and methods of another class (parent)
B Copying code between files
C Objects sharing memory
D Importing modules
Answer: A — A class (child) automatically gets the attributes and methods of another class (parent)
Inheritance lets you build a specialized class on top of a general one: Dog inherits everything Animal has, then adds or changes what it needs. Write the common code once.
Code reuseis-a relationship
Q2.easy
Which syntax makes Dog inherit from Animal?
A class Dog(Animal):
B class Dog extends Animal:
C class Dog inherits Animal:
D class Animal(Dog):
Answer: A — class Dog(Animal):
The parent goes in parentheses after the child's name. (extends is Java; option D is backwards - it would make Animal the child.)
SyntaxParent classes
Q3.easy
class Cat(Animal): pass - and Animal has a sleep() method. What does Cat().sleep() do?
class Animal:
def sleep(self):
print("zzz")
class Cat(Animal):
pass
Cat().sleep() # zzz
A Works - Cat inherited sleep() from Animal
B AttributeError - Cat is empty
C Returns None only
D SyntaxError
Answer: A — Works - Cat inherited sleep() from Animal
An empty child still owns everything the parent defined. Python looks for sleep on Cat, doesn't find it, and walks UP to Animal - found, called.
Method lookupInheritance
Q7.easy
dog = Dog() where Dog inherits Animal. What is isinstance(dog, Animal)?
print(isinstance(dog, Dog)) # True
print(isinstance(dog, Animal)) # True
print(type(dog) == Animal) # False - exact type only
A True - an instance of a subclass IS an instance of the parent too
B False - dog is only a Dog
C Error
D None
Answer: A — True - an instance of a subclass IS an instance of the parent too
isinstance() respects inheritance: every Dog IS an Animal (the 'is-a' relationship). That's why isinstance is preferred over type() == for checks.
isinstance()is-a
Q12.easy
Can a child class ADD methods the parent doesn't have?
class Dog(Animal):
def fetch(self):
print("fetching!")
Dog().fetch() # fine
# Animal().fetch() # AttributeError
A Yes - children extend parents freely (e.g. Dog adds fetch())
B No - children can only override
C Only one new method
D Only attributes, not methods
Answer: A — Yes - children extend parents freely (e.g. Dog adds fetch())
Children = parent's features + anything new. Dog gets eat() and sleep() from Animal and adds fetch(). Note Animal instances do NOT get fetch - inheritance flows downward only.
ExtensionInheritance direction
Q23.easy
In class Rectangle(Shape): - which is the parent (base) class?
A Shape
B Rectangle
C Both
D Neither
Answer: A — Shape
The class in parentheses is the parent (also called base or super class); Rectangle is the child (derived/sub class).
Terminology
Q27.easy
What is the main PURPOSE of inheritance?
A Reuse and organize shared behavior - write common code once in the parent
B Make programs run faster
C Hide code from other developers
D It's required for classes to work
Answer: A — Reuse and organize shared behavior - write common code once in the parent
Shared logic lives once in the base class; specializations only express their differences. Less duplication, clearer structure - though don't force it where 'is-a' doesn't hold.
Code reuseDRY
Medium questions (16)
Q4.medium
Class Dog inherits from Animal. Both define speak(). What runs for Dog().speak()?
class Animal:
def speak(self):
return "..."
class Dog(Animal):
def speak(self):
return "Woof!"
print(Dog().speak()) # Woof!
A Animal.speak() - parents win
B Dog.speak() - the child's version OVERRIDES the parent's
C Both run automatically
D TypeError - ambiguous
Answer: B — Dog.speak() - the child's version OVERRIDES the parent's
Method overriding: Python checks the instance's own class FIRST. Dog.speak() shadows Animal.speak(). The parent's version still exists - reachable via super().speak().
OverridingMRO
Q5.medium
What does super() give you inside a child class?
class Animal:
def __init__(self, name):
self.name = name
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # parent sets name
self.breed = breed # child adds breed
A Access to the PARENT class's methods - e.g. super().__init__(...)
B A more powerful version of self
C The grandchild class
D Administrator rights
Answer: A — Access to the PARENT class's methods - e.g. super().__init__(...)
super() reaches the next class up the chain - most commonly used to run the parent's __init__ so its setup isn't lost when the child overrides it.
A Whether the Dog CLASS inherits from Animal - True here
B Whether dog objects exist
C Whether Animal inherits from Dog
D Nothing - not a real function
Answer: A — Whether the Dog CLASS inherits from Animal - True here
issubclass compares CLASSES (isinstance compares an object to a class). issubclass(Dog, Animal) is True; issubclass(Animal, Dog) is False.
issubclass()Class relationships
Q9.medium
What is polymorphism?
for animal in [Dog(), Cat(), Cow()]:
print(animal.speak()) # Woof! Meow! Moo!
# one loop, three behaviors
A Different classes responding to the SAME method call in their own way
B One class with many names
C Multiple inheritance
D Converting types
Answer: A — Different classes responding to the SAME method call in their own way
Poly = many, morph = forms. Call speak() on any animal - each class provides its own version, and the calling code doesn't care which: the same line of code works for every type.
PolymorphismOverriding
Q11.medium
When the child overrides a method, is the PARENT class changed?
A Car is-a Vehicle; Circle is-a Shape; Admin is-a User
B Car is-a Engine; House is-a Door
C Engine is-a Car
D All pairs fit inheritance
Answer: A — Car is-a Vehicle; Circle is-a Shape; Admin is-a User
Inheritance models 'is-a': every Car IS a Vehicle. A Car HAS an Engine - that's composition (storing an Engine object as an attribute), not inheritance. Choosing wrong leads to awkward classes.
is-a vs has-aComposition
Q15.medium
What is the ultimate ancestor of EVERY Python class?
All classes inherit from object (explicitly or not) - it provides the defaults: __init__, __str__, __eq__ and more. 'class Dog:' and 'class Dog(object):' are identical in Python 3.
objectClass hierarchy
Q16.medium
Is multiple inheritance allowed?
class FlyingCar(Car, Airplane):
class FlyingCar(Car, Airplane):
pass
# lookup order: FlyingCar -> Car -> Airplane -> object
A Yes - a class may inherit from several parents (listed in order)
B No - Python allows only one parent
C Only with a plugin
D Only for built-in types
Answer: A — Yes - a class may inherit from several parents (listed in order)
Python supports multiple inheritance: FlyingCar gets methods from BOTH parents. When both define the same method, the Method Resolution Order decides (left-to-right: Car wins here).
Multiple inheritanceMRO
Q18.medium
In what ORDER does Python search for a method you call on an object?
A The object's class first, then its parent, then grandparent... up to object
B object first, then down
C Random order
D Alphabetical by class name
Answer: A — The object's class first, then its parent, then grandparent... up to object
Lookup climbs the inheritance chain from most-specific to most-general, stopping at the FIRST match - which is exactly why child methods override parent ones.
Method lookupMRO
Q19.medium
Trace the output:
class A:
def greet(self):
return "hi from A"
class B(A):
def greet(self):
return super().greet() + " and B"
print(B().greet())
A hi from A and B
B hi from A
C and B
D Error
Answer: A — hi from A and B
B's greet calls the parent's version via super() (getting 'hi from A') and extends it. This extend-not-replace pattern is what super() is for.
super()Extending methods
Q21.medium
Can you inherit from BUILT-IN types like list?
class Stack(list):
def push(self, x):
self.append(x)
def peek(self):
return self[-1]
s = Stack()
s.push(5)
print(s.peek()) # 5
A Yes - class Stack(list): adds/changes behavior on top of a real list
B No - built-ins are sealed
C Only from str
D Only in Python 2
Answer: A — Yes - class Stack(list): adds/changes behavior on top of a real list
Built-ins are classes like any other. A Stack(list) gets append, len, iteration for free and can add push/peek. (For dict, collections.UserDict is often smoother.)
Subclassing built-ins
Q22.medium
Which line lets Dog's __init__ accept name AND breed while reusing Animal's name setup?
A def __init__(self, name, breed):\n super().__init__(name)\n self.breed = breed
B def __init__(self, breed):\n self.breed = breed
C def __init__(self, name, breed):\n Animal(name)\n self.breed = breed
Accept all needed data, delegate the parent's part to super().__init__(name), then set what's new. Option C creates a THROWAWAY Animal object instead of initializing self.
super()__init__ patterns
Q24.medium
What is the output?
class Bird:
def move(self):
return "fly"
class Penguin(Bird):
def move(self):
return "waddle"
animals = [Bird(), Penguin(), Bird()]
print([a.move() for a in animals])
A ['fly', 'waddle', 'fly']
B ['fly', 'fly', 'fly']
C ['waddle', 'waddle', 'waddle']
D Error
Answer: A — ['fly', 'waddle', 'fly']
Each object uses ITS OWN class's move() - Bird flies, Penguin waddles - even inside one uniform loop. Polymorphism in action.
PolymorphismOverriding
Q26.medium
A child needs the parent's method PLUS extra work. The cleanest pattern is:
class LoggedAccount(Account):
def deposit(self, amt):
super().deposit(amt) # original logic
print(f"logged: +{amt}") # the extra bit
A Override it, call super().method() inside, then add the extra steps
B Copy-paste the parent's code
C Rename the parent method
D Make everything global
Answer: A — Override it, call super().method() inside, then add the extra steps
Extend, don't duplicate: super() runs the original, your code adds the difference. Parent bug-fixes automatically reach the child.
super()DRY principle
Q29.medium
Grandparent -> Parent -> Child. Child calls a method only Grandparent defines. Does it work?
A Yes - lookup climbs the WHOLE chain, however many levels
B No - only direct parents are searched
C Only with two super() calls
D Only if Parent re-exports it
Answer: A — Yes - lookup climbs the WHOLE chain, however many levels
Inheritance chains any depth: Child -> Parent -> Grandparent -> object are all searched in order. Deep hierarchies work - though beyond 2-3 levels they get hard to follow; prefer shallow designs.
Inheritance chainsDesign advice
Q30.medium
What does method overriding let a subclass do?
class Animal:
def speak(self):
return "some sound"
class Cat(Animal):
def speak(self): # overrides
return "Meow"
print(Cat().speak()) # Meow
print(Animal().speak()) # some sound
A Provide its OWN version of a method the parent already defined, replacing it for instances of the subclass
B Delete the parent's method everywhere
C Rename the parent method
D Call two methods at once
Answer: A — Provide its OWN version of a method the parent already defined, replacing it for instances of the subclass
Overriding = redefining an inherited method in the child with the same name. The child's version is used for child instances; the parent (and other subclasses) keep the original. Combine with super() to extend rather than fully replace.
Overridingsuper()Polymorphism
Hard questions (7)
Q6.hard
What breaks here?
class Animal:
def __init__(self, name):
self.name = name
class Dog(Animal):
def __init__(self, breed):
self.breed = breed
d = Dog("lab")
print(d.name)
A AttributeError - Dog's __init__ REPLACED Animal's, so self.name was never set
B Prints lab
C Prints None
D Nothing breaks
Answer: A — AttributeError - Dog's __init__ REPLACED Animal's, so self.name was never set
Overriding __init__ means the parent's initializer does NOT run automatically - name never gets set. Fix: call super().__init__(name) inside Dog's __init__ (and accept name as a parameter).
Forgetting super().__init__() is the #1 inheritance bug.
super()AttributeError
Q10.hard
What is DUCK TYPING?
class Robot: # no Animal parent!
def speak(self):
return "beep"
for thing in [Dog(), Robot()]:
print(thing.speak()) # works - both can speak
A 'If it walks like a duck and quacks like a duck...' - Python cares about what an object CAN DO, not what class it is
B A special duck class in the standard library
C Typing very fast
D Inheritance from birds
Answer: A — 'If it walks like a duck and quacks like a duck...' - Python cares about what an object CAN DO, not what class it is
Python doesn't require a shared parent class for polymorphism: ANY object with a speak() method works in code that calls .speak(). len() works on anything with __len__. Behavior over ancestry.
Duck typingProtocols
Q14.hard
What is COMPOSITION, and when is it better than inheritance?
class Engine:
def start(self): print("vroom")
class Car:
def __init__(self):
self.engine = Engine() # has-a
def start(self):
self.engine.start()
A Storing other objects as attributes ('has-a') - better when the relationship isn't truly 'is-a'
B Combining two classes into one file
C A stricter form of inheritance
D Composing music with Python
Answer: A — Storing other objects as attributes ('has-a') - better when the relationship isn't truly 'is-a'
A Car HAS an Engine, so Engine is an attribute - not a parent. Composition is flexible (swap engines at runtime, no fragile hierarchies). Modern advice: 'prefer composition over inheritance' unless the is-a test clearly passes.
CompositionDesign principles
Q17.hard
class C(A, B) - both A and B define hello(). Which runs for C().hello()?
class A:
def hello(self): return "A"
class B:
def hello(self): return "B"
class C(A, B): pass
print(C().hello()) # A
print(C.__mro__) # (C, A, B, object)
A A's - the MRO checks parents LEFT to RIGHT as listed
B B's - rightmost wins
C Both run
D TypeError - conflict
Answer: A — A's - the MRO checks parents LEFT to RIGHT as listed
The Method Resolution Order for C(A, B) is C -> A -> B -> object. First match wins: A.hello(). Inspect any class's order with C.__mro__ or C.mro().
MROMultiple inheritance
Q20.hard
How do you make a 'template' method that children MUST implement?
class Shape:
def area(self):
raise NotImplementedError("subclass must define area")
class Circle(Shape):
def area(self):
return 3.14159 * self.r ** 2
A Raise NotImplementedError in the parent (or use the abc module's @abstractmethod)
B Leave the method out entirely
C Name it _must_implement
D Python can't express this
Answer: A — Raise NotImplementedError in the parent (or use the abc module's @abstractmethod)
The simple way: the parent's method raises NotImplementedError, so forgetting to override it fails loudly. The formal way: inherit from abc.ABC and mark methods @abstractmethod - then instantiating an incomplete child is impossible.
Abstract classesNotImplementedErrorabc module
Q25.hard
Why is 'type(x) == Dog' usually WORSE than 'isinstance(x, Dog)'?
class Puppy(Dog): pass
p = Puppy()
print(type(p) == Dog) # False!
print(isinstance(p, Dog)) # True
A type() ignores inheritance - a Puppy(Dog) instance would FAIL the type check but pass isinstance
B type() is slower
C type() doesn't exist
D They're always identical
Answer: A — type() ignores inheritance - a Puppy(Dog) instance would FAIL the type check but pass isinstance
Exact-type checks break polymorphism: code demanding type(x) == Dog rejects perfectly good subclasses. isinstance accepts the whole family - almost always what you want.
isinstance()Liskov principle
Q28.hard
What is the 'diamond problem', and how does Python cope?
class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass
print(D.__mro__) # D, B, C, A, object
A D inherits from B and C which both inherit A - Python's MRO (C3 linearization) gives ONE consistent lookup order, each class visited once
B A crash when classes form a square
C Python forbids the diamond shape entirely
D Methods run twice
Answer: A — D inherits from B and C which both inherit A - Python's MRO (C3 linearization) gives ONE consistent lookup order, each class visited once
The diamond: D(B, C), B(A), C(A). Which A-method does D see, and does A run twice? Python's C3 algorithm computes a deterministic order (D, B, C, A) and super() follows it - so cooperative calls visit each class exactly once.