Interview Prep — OOP Track
OOP Interview Questions
15 OOP questions — the four pillars, SOLID principles, design patterns, and Python-specific OOP features. Tagged for FAANG and startups.
PillarsSOLIDPatternsPython OOP
15Total
4Pillars
5SOLID
3Patterns
3Python OOP
01
What are the four pillars of OOP?
OOP
▾
- Encapsulation: Bundling data and methods together; hiding internal state. Achieved via private/protected attributes in Python (
_name,__name). - Abstraction: Exposing only essential features, hiding complexity. Abstract classes/interfaces define contracts.
- Inheritance: A child class inherits attributes and methods from a parent class, enabling code reuse.
- Polymorphism: Same interface, different behaviour. Method overriding (runtime) and method overloading (compile-time — not natively in Python).
02
What is polymorphism? Explain with a Python example.
OOP
▾
Polymorphism means "many forms" — objects of different classes can be treated through a common interface.
Types: Compile-time (overloading), Runtime (overriding), Duck typing.
python
class Dog: def speak(self): return "Woof!" class Cat: def speak(self): return "Meow!" def make_sound(animal): print(animal.speak()) make_sound(Dog()) # Woof! make_sound(Cat()) # Meow!
03
What is encapsulation? How is it achieved in Python?
OOP
▾
Encapsulation bundles data and methods together while restricting direct access to internal state.
Python access conventions:
Python access conventions:
public— normal attribute (accessible everywhere)_protected— convention, "please don't touch" (still accessible)__private— name-mangled, harder to access (_ClassName__attr)
python
class BankAccount: def __init__(self): self.balance = 0 # public self._pin = "1234" # protected self.__secret = "xyz" # private def get_balance(self): return self.balance
04
What is abstraction? How is it different from encapsulation?
OOP
▾
Abstraction hides complex implementation details and shows only essential features.
Encapsulation hides internal state and requires interaction through methods.
Key difference: Abstraction = hiding complexity (design level). Encapsulation = hiding data (implementation level).
Encapsulation hides internal state and requires interaction through methods.
Key difference: Abstraction = hiding complexity (design level). Encapsulation = hiding data (implementation level).
python
from abc import ABC, abstractmethod class Payment(ABC): @abstractmethod def process_payment(self, amount): pass class CreditCard(Payment): def process_payment(self, amount): print(f"Processing ${amount} via Credit Card")
05
Explain SOLID, one line each.
OOP
▾
Single responsibility: a class has one reason to change. Open/closed: open to extension, closed to modification. Liskov substitution: a subtype must work anywhere its base does. Interface segregation: many small interfaces beat one fat one. Dependency inversion: depend on abstractions, not concretions. They describe the same goal from five angles — change one part without breaking the others.
06
What is coupling and cohesion, and which way do you want each?
OOP
▾
Coupling is how much one module depends on another; cohesion is how related the things inside one module are. You want low coupling and high cohesion. A class that reaches into another's internals is tightly coupled, so a change there breaks it here. A class holding unrelated helpers has low cohesion, so every feature touches it and nothing has an obvious home.
07
What is the Law of Demeter, and what does breaking it look like?
OOP
▾
Talk only to your immediate collaborators. The tell is a chain:
order.customer.address.city — that code now depends on three classes' internals, so a change to any of them breaks it. Ask the object for what you need (order.shipping_city()) rather than reaching through it.
08
Overloading vs overriding — and does Python have both?
OOP
▾
Overriding replaces an inherited method in a subclass; Python has it, resolved through the MRO at call time. Overloading means several methods with the same name and different signatures; Python does not have it — a later definition simply replaces the earlier one. The equivalents are default arguments,
*args, or functools.singledispatch for genuine type-based dispatch.
09
What is an abstract class, and what happens if you instantiate one?
OOP
▾
A class that declares methods subclasses must implement and cannot itself be instantiated. In Python it comes from
abc.ABC plus @abstractmethod; instantiating one raises TypeError listing the unimplemented methods. It exists to state a contract in code rather than a comment — the failure arrives at construction, not at the first call. Python OOP
10
Explain inheritance types in Python with examples.
OOP
▾
- Single:
class B(A) - Multiple:
class C(A, B)— Python uses MRO (Method Resolution Order) / C3 linearization - Multilevel: A → B → C (chain of inheritance)
- Hierarchical: Multiple children from one parent
- Hybrid: Combination of the above types
MRO: Use
ClassName.__mro__ or help(ClassName) to inspect the resolution order in multiple inheritance.
11
What is the difference between @classmethod, @staticmethod, and instance method?
OOP
▾
python
class MyClass: count = 0 def instance_method(self): # access self & class return self @classmethod def class_method(cls): # access class, not instance return cls.count @staticmethod def static_method(x): # no self or cls return x * 2
| Method type | First arg | Use case |
|---|---|---|
| Instance method | self | Access/modify instance state |
| Class method | cls | Factory methods, alternative constructors |
| Static method | — | Utility functions logically belonging to the class |
12
What are dunder (magic) methods in Python?
OOP
▾
Dunder methods (double underscore) let you define how objects behave with built-in operators and functions.
__init__— constructor__str__,__repr__— string representation__len__—len(obj)__eq__,__lt__,__gt__— comparison operators__add__,__mul__— arithmetic operators__getitem__,__setitem__— indexingobj[key]__iter__,__next__— make object iterable__enter__,__exit__— context manager protocol
python
class Vector: def __init__(self, x, y): self.x, self.y = x, y def __add__(self, other): return Vector(self.x + other.x, self.y + other.y) def __repr__(self): return f"Vector({self.x}, {self.y})" v = Vector(1, 2) + Vector(3, 4) # Vector(4, 6)
13
What is the difference between abstract class and interface in Python?
OOP
▾
| Aspect | Abstract Class | Interface (Python pattern) |
|---|---|---|
| Concrete methods | Allowed | Not allowed (all abstract) |
| State | Can have instance variables | Typically no state |
| Multiple inherit | Possible but complex | Encouraged |
| Python syntax | ABC + mix of methods | ABC with all @abstractmethod |
Note: Python prefers "duck typing" over strict interfaces — "if it walks like a duck and quacks like a duck, it's a duck."
14
What are Python decorators in the context of classes?
OOP
▾
Common class decorators:
@property— getter method that looks like an attribute@x.setter— setter with validation@classmethod— receives class as first argument@staticmethod— noselforcls
python
class Person: def __init__(self, name): self._name = name @property def name(self): return self._name.title() @name.setter def name(self, value): if not value: raise ValueError("Name required") self._name = value p = Person("alice") print(p.name) # Alice (via @property getter) p.name = "bob" # calls @name.setter
15
What is
__slots__ and when should you use it?
OOP
▾
__slots__ restricts allowed attributes and saves memory by preventing automatic __dict__ creation per instance.
python
class Point: __slots__ = ['x', 'y'] def __init__(self, x, y): self.x = x self.y = y # p.z = 1 → AttributeError: 'Point' object has no attribute 'z'
- 40–50% memory reduction when creating many instances
- Faster attribute access
16
What is the difference between
__str__ and __repr__?
OOP
▾
__repr__ is for developers: unambiguous, ideally something that could reconstruct the object, and what you see in the REPL or a traceback. __str__ is for users: readable, what print() and str() give you. If you write only one, write __repr__ — __str__ falls back to it, but not the other way round.
17
How do you make a class support
== and use it in a set?
OOP
▾
Implement
__eq__ for equality and __hash__ for hashing, over the same fields. Defining __eq__ alone sets __hash__ to None, making the class unhashable — that is Python protecting you from objects that compare equal but hash differently. @dataclass(frozen=True) generates both correctly, which is usually the right answer.
18
What is a property, and why not just use a public attribute?
OOP
▾
@property turns a method into a read that looks like an attribute, with an optional @x.setter. Start with a plain public attribute — Python has no need for Java-style getters. Reach for a property only when you later need validation, a computed value, or a deprecation shim, and the point is that you can add it without changing a single caller.
19
What is duck typing, and how do protocols fit in?
OOP
▾
Python dispatches on behaviour, not declared type: if an object has the method you call, it works. That is why functions take "something iterable" rather than a specific class.
typing.Protocol makes that contract checkable — a static type describing the methods required, with no inheritance needed, so existing classes satisfy it retroactively.
20
What is the difference between a class attribute and an instance attribute?
OOP
▾
A class attribute lives on the class and is shared by every instance; an instance attribute is created per object, usually in
__init__. Assigning through an instance creates an instance attribute that shadows the class one. class Dog:
tricks = [] # shared by every dog — the classic bug
def __init__(self, name):
self.name = name # per instance
a, b = Dog("a"), Dog("b")
a.tricks.append("sit")
b.tricks # ["sit"] — same list Advanced OOP
21
What is the Diamond Problem in inheritance? How does Python solve it?
OOP
▾
The Diamond Problem occurs when a class inherits from two classes that both inherit from a common base — creating ambiguity about which path to follow.
Python's solution: C3 Linearization creates a consistent, deterministic MRO.
text
A
/ \
B C
\ /
D ← Which A.__init__ does D call?
python
class A: def hello(self): print("A") class B(A): def hello(self): print("B"); super().hello() class C(A): def hello(self): print("C"); super().hello() class D(B, C): pass D().hello() # B → C → A (MRO order) print(D.__mro__) # (D, B, C, A, object)
Rule of thumb: Always use
super() in cooperative multiple inheritance so the MRO chain is properly followed.
22
What is composition? When should you prefer it over inheritance?
OOP
▾
Composition = "has-a" relationship (object contains other objects).
Inheritance = "is-a" relationship (child is a specialised parent).
Prefer composition when:
Inheritance = "is-a" relationship (child is a specialised parent).
python
# Inheritance: Car IS-A Vehicle class Car(Vehicle): ... # Composition: Car HAS-A Engine class Engine: def start(self): print("Engine started") class Car: def __init__(self): self.engine = Engine() # composed def start(self): self.engine.start()
- You need to change behaviour at runtime (swap out the component)
- Inheritance would create tight coupling
- Multiple inheritance becomes complex or fragile
Principle: "Favor composition over inheritance" (Gang of Four) — more flexible, easier to test.
23
Explain the Singleton pattern and how to implement it in Python.
OOP
▾
Singleton ensures a class has only one instance and provides global access to it.
Use cases: Database connections, logging, configuration managers.
python
# Method 1: __new__ class Singleton: _instance = None def __new__(cls, *args, **kwargs): if not cls._instance: cls._instance = super().__new__(cls) return cls._instance # Method 2: via Metaclass class SingletonMeta(type): _instances = {} def __call__(cls, *args, **kwargs): if cls not in cls._instances: cls._instances[cls] = super().__call__(*args, **kwargs) return cls._instances[cls] # Verify a, b = Singleton(), Singleton() print(a is b) # True
Caution: Singletons can make testing harder — consider dependency injection as an alternative.
24
What is the difference between shallow copy and deep copy in OOP?
OOP
▾
| Aspect | Shallow Copy | Deep Copy |
|---|---|---|
| Top-level object | New object created | New object created |
| Nested objects | References copied (shared) | Recursively duplicated |
| Mutation risk | Mutating nested obj affects both | Fully independent |
| Performance | Faster | Slower (recursive) |
python
import copy class Address: def __init__(self, city): self.city = city class Person: def __init__(self, addr): self.addr = addr p1 = Person(Address("NYC")) p2 = copy.copy(p1) # shallow — p2.addr is p1.addr p3 = copy.deepcopy(p1) # deep — p3.addr is a new object p2.addr.city = "LA" print(p1.addr.city) # LA ← shallow copy shares nested obj print(p3.addr.city) # NYC ← deep copy is independent
25
What are metaclasses in Python?
OOP
▾
A metaclass is the "class of a class" — it defines how classes themselves behave, just as classes define how instances behave.
type is the default metaclass for all Python classes. Custom metaclasses inherit from type.
python
# Every class is an instance of its metaclass print(type(int)) # <class 'type'> print(type(type)) # <class 'type'> # Custom metaclass: enforce method naming convention class EnforceLower(type): def __new__(mcs, name, bases, namespace): for key in namespace: if key.startswith('_'): continue if key != key.lower(): raise TypeError(f"Method '{key}' must be lowercase") return super().__new__(mcs, name, bases, namespace) class MyModel(metaclass=EnforceLower): def save(self): pass # OK # def Save(self): pass # → TypeError
Use sparingly: Metaclasses are powerful but add significant complexity. ORMs (like Django), API frameworks, and dataclasses use them internally.
26
What are the four main creational patterns, in one line each?
OOP
▾
Factory method: a method decides which subclass to build. Abstract factory: one object builds a whole family of related products. Builder: assemble a complex object step by step, so the constructor does not take twelve arguments. Prototype: clone an existing instance instead of constructing from scratch. All four exist to keep the choice of concrete class out of the calling code.
27
Explain the observer pattern and where you have already used it.
OOP
▾
One subject keeps a list of observers and notifies them when its state changes, so publishers know nothing about subscribers. You have used it every time you wrote
addEventListener, subscribed to a store, or connected a signal. The trap is lifetime: an observer that never unsubscribes keeps the subject holding a reference to it, which is a leak.
28
What is the strategy pattern, and what does it replace?
OOP
▾
Interchangeable algorithms behind one interface, chosen at runtime. It replaces the growing
if/elif chain that picks behaviour by type — each branch becomes its own object, and adding a case stops meaning editing the dispatcher. In Python a strategy is often just a function passed in; you do not need a class per algorithm.
29
What is dependency injection, and why does it matter for tests?
OOP
▾
Passing a collaborator in rather than constructing it inside. A class that builds its own database client can only ever be tested against a real database; one that accepts a client can be handed a fake. That is the whole benefit — it turns a hard dependency into a parameter, which is also what makes the dependency visible in the signature.
30
When is inheritance the wrong tool, and what are the symptoms?
OOP
▾
When the relationship is not genuinely is-a. Symptoms: a subclass overriding a method to raise
NotImplementedError; a base class growing flags so it can behave differently per subclass; a hierarchy more than two or three deep; needing to know which subclass you have. Each of those is composition or a strategy wearing an inheritance costume.No questions match your search.