Top 20 OOP Interview Questions and Answers [2025]
Master OOP interview questions with detailed answers and Python examples. Cover encapsulation, inheritance, polymorphism, abstraction, SOLID principles, and design patterns for coding interviews

The Interview That Changed Everything
"Tell me about the four pillars of OOP," the interviewer asked.
I froze. I knew encapsulation, inheritance... but what were the other two? I fumbled through an answer about "classes and objects" (wrong!).
I didn't get that job.
After that interview, I spent weeks mastering OOP concepts. I practiced every common OOP interview question until I could answer them in my sleep.
Six months later, I aced three back-to-back interviews. Different companies, same questions. I was prepared.
Today, I'm sharing the Top 20 OOP interview questions I've encountered—with detailed answers and code examples you can use in your next interview.
Part 1: Core OOP Concepts
Q1: What are the four pillars of OOP?
Answer:
The four pillars of Object-Oriented Programming are:
- Encapsulation - Bundling data and methods together; hiding internal details
- Inheritance - Reusing code through parent-child class relationships
- Polymorphism - Same interface, different implementations
- Abstraction - Hiding complex implementation details; showing only essentials
Example:
# 1. Encapsulation
class BankAccount:
def __init__(self, balance):
self.__balance = balance # Private attribute
def deposit(self, amount):
self.__balance += amount # Controlled access
# 2. Inheritance
class SavingsAccount(BankAccount):
def __init__(self, balance, interest_rate):
super().__init__(balance)
self.interest_rate = interest_rate
# 3. Polymorphism
class Dog:
def speak(self): return "Woof!"
class Cat:
def speak(self): return "Meow!"
def make_sound(animal): # Same method, different behavior
print(animal.speak())
# 4. Abstraction
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self): # Abstract method
pass
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self): # Concrete implementation
return 3.14 * self.radius ** 2
Follow-up: "Give a real-world example of each pillar."
Q2: What is the difference between a class and an object?
Answer:
- Class is a blueprint or template that defines attributes and methods
- Object is an instance of a class; the actual entity created from the blueprint
Analogy: Class is like a cookie cutter, objects are the actual cookies.
# Class (blueprint)
class Car:
def __init__(self, brand, model):
self.brand = brand
self.model = model
def start(self):
print(f"{self.brand} {self.model} is starting...")
# Objects (instances)
car1 = Car("Toyota", "Camry")
car2 = Car("Honda", "Accord")
# Each object has its own data
car1.start() # Toyota Camry is starting...
car2.start() # Honda Accord is starting...
Q3: What is encapsulation? Why is it important?
Answer:
Encapsulation is bundling data (attributes) and methods that operate on that data together in a class, and restricting direct access to some components.
Benefits:
- Data protection - Prevents direct modification
- Validation - Control how data is accessed/modified
- Flexibility - Change internal implementation without affecting external code
- Security - Hide sensitive data
class User:
def __init__(self, username, password):
self.username = username
self.__password = password # Private (name mangling)
def verify_password(self, input_password):
"""Controlled access to password"""
return self.__password == input_password
def change_password(self, old_pass, new_pass):
"""Validated password change"""
if not self.verify_password(old_pass):
raise ValueError("Current password is incorrect")
if len(new_pass) < 8:
raise ValueError("Password must be at least 8 characters")
self.__password = new_pass
print("Password changed successfully")
user = User("alice", "secret123")
# Cannot access password directly
# print(user.__password) # AttributeError!
# Must use methods (controlled access)
print(user.verify_password("secret123")) # True
user.change_password("secret123", "newSecret456")
Q4: Explain inheritance with an example. What are its types?
Answer:
Inheritance allows a class (child/derived) to inherit attributes and methods from another class (parent/base), promoting code reuse.
Types of inheritance:
- Single - One parent, one child
- Multiple - Multiple parents, one child
- Multilevel - Grandparent → Parent → Child
- Hierarchical - One parent, multiple children
- Hybrid - Combination of above
# Single Inheritance
class Animal:
def eat(self):
print("Eating...")
class Dog(Animal):
def bark(self):
print("Woof!")
dog = Dog()
dog.eat() # Inherited
dog.bark() # Own method
# Multiple Inheritance
class Flyable:
def fly(self):
print("Flying...")
class Swimmable:
def swim(self):
print("Swimming...")
class Duck(Animal, Flyable, Swimmable):
pass
duck = Duck()
duck.eat() # From Animal
duck.fly() # From Flyable
duck.swim() # From Swimmable
# Multilevel Inheritance
class Vehicle:
def start(self):
print("Starting vehicle...")
class Car(Vehicle):
def drive(self):
print("Driving car...")
class ElectricCar(Car):
def charge(self):
print("Charging battery...")
tesla = ElectricCar()
tesla.start() # From Vehicle
tesla.drive() # From Car
tesla.charge() # Own method
Follow-up: "What is the diamond problem in multiple inheritance?"
Answer: When a class inherits from two classes that share a common parent, causing ambiguity. Python resolves this using Method Resolution Order (MRO).
Q5: What is polymorphism? Explain with examples.
Answer:
Polymorphism means "many forms" - same interface, different implementations.
Two types:
- Compile-time (Method Overloading) - Same method name, different parameters
- Runtime (Method Overriding) - Child class redefines parent method
# Runtime Polymorphism (Method Overriding)
class Shape:
def area(self):
return 0
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self): # Override
return self.width * self.height
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self): # Override
return 3.14 * self.radius ** 2
# Polymorphic behavior
def print_area(shape: Shape):
print(f"Area: {shape.area()}")
shapes = [Rectangle(5, 10), Circle(7)]
for shape in shapes:
print_area(shape) # Same method call, different outputs
# Output:
# Area: 50
# Area: 153.86
Part 2: Advanced OOP Concepts
Q6: What is abstraction? How is it different from encapsulation?
Answer:
Abstraction hides complex implementation details and shows only essential features.
Difference:
- Abstraction - What to do (interface)
- Encapsulation - How to do it (implementation hiding)
from abc import ABC, abstractmethod
# Abstraction: Define WHAT to do
class PaymentMethod(ABC):
@abstractmethod
def process_payment(self, amount):
pass
# Concrete implementations define HOW
class CreditCard(PaymentMethod):
def __init__(self, card_number):
self.__card_number = card_number # Encapsulation
def process_payment(self, amount):
print(f"Processing ${amount} via credit card")
# Complex payment logic hidden
class PayPal(PaymentMethod):
def __init__(self, email):
self.__email = email # Encapsulation
def process_payment(self, amount):
print(f"Processing ${amount} via PayPal")
# Complex PayPal logic hidden
# User doesn't know HOW payment is processed
def checkout(payment: PaymentMethod, amount):
payment.process_payment(amount)
checkout(CreditCard("****1234"), 99.99)
checkout(PayPal("user@email.com"), 149.99)
Q7: What are abstract classes? When would you use them?
Answer:
Abstract class is a class that:
- Cannot be instantiated directly
- Contains abstract methods (no implementation) that must be implemented by child classes
- Can have concrete methods (with implementation)
Use when:
- You want to define a common interface for related classes
- You have shared implementation that subclasses can use
- You want to enforce certain methods in all subclasses
from abc import ABC, abstractmethod
class Database(ABC):
def __init__(self, connection_string):
self.connection_string = connection_string
# Concrete method (shared by all)
def log_query(self, query):
print(f"Executing: {query}")
# Abstract methods (must implement)
@abstractmethod
def connect(self):
pass
@abstractmethod
def execute(self, query):
pass
class MySQLDatabase(Database):
def connect(self):
print(f"Connecting to MySQL: {self.connection_string}")
def execute(self, query):
self.log_query(query) # Use inherited method
print("Executing MySQL query")
class PostgreSQLDatabase(Database):
def connect(self):
print(f"Connecting to PostgreSQL: {self.connection_string}")
def execute(self, query):
self.log_query(query) # Use inherited method
print("Executing PostgreSQL query")
# Cannot instantiate abstract class
# db = Database("connection") # TypeError!
# Must use concrete classes
mysql = MySQLDatabase("mysql://localhost:3306")
postgres = PostgreSQLDatabase("postgresql://localhost:5432")
mysql.connect()
mysql.execute("SELECT * FROM users")
Q8: What are interfaces? How are they different from abstract classes?
Answer:
| Feature | Abstract Class | Interface |
|---|---|---|
| Implementation | Can have concrete methods | Only method signatures (Python uses ABC) |
| Constructor | Yes | No |
| Multiple Inheritance | No (single) | Yes (multiple) |
| When to Use | Shared implementation + interface | Pure contract |
Python Note: Python doesn't have formal interfaces, but uses abstract base classes with all abstract methods.
from abc import ABC, abstractmethod
# "Interface" in Python (abstract class with all abstract methods)
class Drawable(ABC):
@abstractmethod
def draw(self):
pass
class Clickable(ABC):
@abstractmethod
def on_click(self):
pass
# Class can "implement" multiple "interfaces"
class Button(Drawable, Clickable):
def draw(self):
print("Drawing button...")
def on_click(self):
print("Button clicked!")
button = Button()
button.draw()
button.on_click()
Q9: What is method overriding vs method overloading?
Answer:
Method Overriding:
- Child class redefines parent class method
- Same method name, same parameters
- Runtime polymorphism
Method Overloading:
- Multiple methods with same name, different parameters
- Compile-time polymorphism
- Note: Python doesn't support traditional overloading (last definition wins)
# Method Overriding
class Animal:
def make_sound(self):
return "Some sound"
class Dog(Animal):
def make_sound(self): # Override
return "Woof!"
dog = Dog()
print(dog.make_sound()) # "Woof!"
# Method Overloading (Python workaround using default arguments)
class Calculator:
def add(self, a, b=None, c=None):
if b is None:
return a
elif c is None:
return a + b
else:
return a + b + c
calc = Calculator()
print(calc.add(5)) # 5
print(calc.add(5, 3)) # 8
print(calc.add(5, 3, 2)) # 10
Q10: What is super() and why is it used?
Answer:
super() is used to call methods from a parent class. It's commonly used to:
- Call parent constructor
- Extend parent method behavior
- Navigate MRO (Method Resolution Order) in multiple inheritance
class Vehicle:
def __init__(self, brand, model):
self.brand = brand
self.model = model
print(f"Vehicle initialized: {brand} {model}")
def start(self):
print("Starting vehicle...")
class Car(Vehicle):
def __init__(self, brand, model, doors):
super().__init__(brand, model) # Call parent constructor
self.doors = doors
print(f"Car initialized with {doors} doors")
def start(self):
super().start() # Call parent method
print("Car-specific start logic")
car = Car("Toyota", "Camry", 4)
# Output:
# Vehicle initialized: Toyota Camry
# Car initialized with 4 doors
car.start()
# Output:
# Starting vehicle...
# Car-specific start logic
Part 3: Design Principles & Patterns
Q11: Explain SOLID principles briefly.
Answer:
- S - Single Responsibility Principle: Class should have one reason to change
- O - Open/Closed Principle: Open for extension, closed for modification
- L - Liskov Substitution Principle: Subclasses should be substitutable for their base class
- I - Interface Segregation Principle: Many specific interfaces > one general interface
- D - Dependency Inversion Principle: Depend on abstractions, not concretions
# S - Single Responsibility
class User:
pass # Only user data
class UserRepository:
pass # Only database operations
class EmailService:
pass # Only email sending
# O - Open/Closed
class PaymentMethod(ABC):
@abstractmethod
def pay(self, amount): pass
class CreditCard(PaymentMethod): # Extend, don't modify
def pay(self, amount):
print(f"Paying ${amount} via credit card")
# L - Liskov Substitution
def process_payment(method: PaymentMethod, amount):
method.pay(amount) # Works with any PaymentMethod subclass
# I - Interface Segregation
class Printable(ABC):
@abstractmethod
def print(self): pass
class Scannable(ABC):
@abstractmethod
def scan(self): pass
# D - Dependency Inversion
class UserService:
def __init__(self, database: IDatabase): # Depend on interface
self.database = database
Q12: What is composition vs inheritance?
Answer:
- Inheritance - "is-a" relationship (Dog is-an Animal)
- Composition - "has-a" relationship (Car has-an Engine)
Favor composition over inheritance when:
- You need flexibility
- Multiple behaviors need to be combined
- Avoiding deep hierarchies
# Inheritance
class Animal:
def eat(self): print("Eating...")
class Dog(Animal):
def bark(self): print("Woof!")
# Composition
class Engine:
def start(self): print("Engine started")
class Wheels:
def rotate(self): print("Wheels rotating")
class Car:
def __init__(self):
self.engine = Engine() # Has-an Engine
self.wheels = Wheels() # Has-a Wheels
def drive(self):
self.engine.start()
self.wheels.rotate()
car = Car()
car.drive()
Follow-up: "When would you choose inheritance over composition?"
Answer: When there's a clear "is-a" relationship and shared behavior across all subclasses.
Q13: What are static methods and class methods?
Answer:
- Instance method - Operates on instance (
self) - Class method - Operates on class (
cls) - Static method - Utility function, no access to instance or class
class User:
user_count = 0 # Class variable
def __init__(self, name):
self.name = name # Instance variable
User.user_count += 1
# Instance method
def greet(self):
print(f"Hello, I'm {self.name}")
# Class method
@classmethod
def get_user_count(cls):
return cls.user_count
# Static method
@staticmethod
def is_valid_username(username):
return len(username) >= 3
user1 = User("Alice")
user2 = User("Bob")
user1.greet() # Instance method
print(User.get_user_count()) # Class method: 2
print(User.is_valid_username("Jo")) # Static method: False
Q14: What are properties (getters/setters)?
Answer:
Properties provide controlled access to attributes with validation.
class Temperature:
def __init__(self, celsius):
self._celsius = celsius
@property
def celsius(self):
"""Getter"""
return self._celsius
@celsius.setter
def celsius(self, value):
"""Setter with validation"""
if value < -273.15:
raise ValueError("Below absolute zero!")
self._celsius = value
@property
def fahrenheit(self):
"""Computed property"""
return self._celsius * 9/5 + 32
temp = Temperature(25)
print(temp.celsius) # Getter: 25
print(temp.fahrenheit) # Computed: 77.0
temp.celsius = 30 # Setter
try:
temp.celsius = -300 # Validation error
except ValueError as e:
print(e) # Below absolute zero!
Q15: Explain the __init__ and __new__ methods.
Answer:
__new__- Creates the instance (constructor)__init__- Initializes the instance
Execution order: __new__ → __init__
class MyClass:
def __new__(cls, *args, **kwargs):
print("__new__ called (creates instance)")
instance = super().__new__(cls)
return instance
def __init__(self, value):
print("__init__ called (initializes instance)")
self.value = value
obj = MyClass(42)
# Output:
# __new__ called (creates instance)
# __init__ called (initializes instance)
# Use case for __new__: Singleton pattern
class Singleton:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
s1 = Singleton()
s2 = Singleton()
print(s1 is s2) # True (same instance)
Part 4: Advanced & Tricky Questions
Q16: What is the difference between __str__ and __repr__?
Answer:
__str__- Human-readable string (for end users)__repr__- Developer-friendly representation (for debugging)
Rule: repr(obj) should ideally be valid Python code to recreate the object.
class Book:
def __init__(self, title, author):
self.title = title
self.author = author
def __str__(self):
"""For print() and str()"""
return f"{self.title} by {self.author}"
def __repr__(self):
"""For debugging and repr()"""
return f"Book(title='{self.title}', author='{self.author}')"
book = Book("1984", "George Orwell")
print(str(book)) # 1984 by George Orwell
print(repr(book)) # Book(title='1984', author='George Orwell')
# In interactive shell
book # Uses __repr__
Q17: What is the Method Resolution Order (MRO)?
Answer:
MRO determines the order in which Python looks for methods in a class hierarchy, especially with multiple inheritance.
C3 Linearization Algorithm is used.
class A:
def method(self):
print("A")
class B(A):
def method(self):
print("B")
class C(A):
def method(self):
print("C")
class D(B, C):
pass
d = D()
d.method() # Output: B
# Check MRO
print(D.__mro__)
# (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)
# Or use mro() method
print(D.mro())
Q18: What are magic/dunder methods?
Answer:
Magic methods (double underscore methods) define how objects behave with built-in operations.
Common ones:
__init__- Initialization__str__,__repr__- String representation__len__- Length__eq__,__lt__,__gt__- Comparison__add__,__sub__- Arithmetic__getitem__,__setitem__- Indexing
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"Vector({self.x}, {self.y})"
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __len__(self):
return int((self.x**2 + self.y**2) ** 0.5)
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2) # Vector(4, 6) using __add__
print(v1 == v2) # False using __eq__
print(len(v1)) # 2 using __len__
Q19: What are descriptors?
Answer:
Descriptors are objects that define how attribute access is handled using __get__, __set__, and __delete__ methods.
Use cases: Validation, type checking, lazy loading
class Positive:
def __init__(self, name):
self.name = name
def __get__(self, obj, objtype=None):
return obj.__dict__.get(self.name, 0)
def __set__(self, obj, value):
if value < 0:
raise ValueError(f"{self.name} must be positive")
obj.__dict__[self.name] = value
class Product:
price = Positive("price")
quantity = Positive("quantity")
def __init__(self, price, quantity):
self.price = price
self.quantity = quantity
product = Product(10, 5)
print(product.price) # 10
try:
product.price = -5 # Validation error
except ValueError as e:
print(e) # price must be positive
Q20: How do you implement a Singleton pattern in Python?
Answer:
Singleton ensures a class has only one instance.
# Method 1: Using __new__
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, value):
self.value = value
s1 = Singleton(10)
s2 = Singleton(20)
print(s1 is s2) # True
print(s1.value) # 20 (both point to same instance)
# Method 2: Using decorator
def singleton(cls):
instances = {}
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
@singleton
class Database:
def __init__(self, connection_string):
self.connection_string = connection_string
db1 = Database("mysql://localhost")
db2 = Database("postgresql://localhost")
print(db1 is db2) # True
Interview Tips
How to Answer OOP Questions:
- Start with definition - Clear, concise explanation
- Explain why it matters - Benefits, use cases
- Give a code example - Simple, relevant code
- Mention trade-offs - When to use, when not to
- Follow up with real-world - Where you've used it
Common Follow-ups:
- "Give a real-world example"
- "When would you not use this?"
- "What are the trade-offs?"
- "How does this work in Python specifically?"
Red Flags to Avoid:
- ❌ Theoretical answers without examples
- ❌ Code examples with syntax errors
- ❌ Saying "I don't know" without trying
- ❌ Overcomplicating simple concepts
Conclusion
Key preparation steps:
- ✅ Understand the four pillars deeply
- ✅ Practice coding examples for each concept
- ✅ Learn SOLID principles
- ✅ Know common design patterns
- ✅ Practice explaining concepts out loud
Remember: Interviewers want to see:
- Clear understanding of concepts
- Ability to write clean code
- Real-world application knowledge
- Problem-solving skills
Practice these questions, understand the concepts, and you'll ace your OOP interviews!
Preparing for coding interviews? I'd love to hear about your journey! Connect with me on Twitter or LinkedIn!
Support My Work
If this guide helped you with this topic, I'd really appreciate your support! Creating comprehensive, free content like this takes significant time and effort. Your support helps me continue sharing knowledge and creating more helpful resources for developers.
☕ Buy me a coffee - Every contribution, big or small, means the world to me and keeps me motivated to create more content!
Cover image by Jakub Żerdzicki on Unsplash