Concise notes & sample answers for interview preparation — focus on practical OOP design, common scenarios, and Python code examples. Designed for automation/QA engineers preparing for interviews.
Below are frequently asked scenarios. For each: problem, approach, and a compact Python implementation.
BankAccount
class (deposit, withdraw, transfer)class BankAccount:
def __init__(self, owner: str, balance: float = 0.0):
self.owner = owner
self._balance = float(balance)
@property
def balance(self):
return self._balance
def deposit(self, amount: float):
if amount <= 0:
raise ValueError('Deposit must be positive')
self._balance += amount
def withdraw(self, amount: float):
if amount <= 0:
raise ValueError('Withdraw must be positive')
if amount > self._balance:
raise ValueError('Insufficient funds')
self._balance -= amount
def transfer_to(self, other_account, amount: float):
self.withdraw(amount)
other_account.deposit(amount)
# Usage
# a = BankAccount('Alice', 100)
# b = BankAccount('Bob')
# a.transfer_to(b, 30)
Interview tip: mention thread-safety if asked (use locks for concurrent access) and input validation.
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]
class Config(metaclass=SingletonMeta):
def __init__(self):
self.value = 42
# Usage: only one Config instance will exist
Interviewers might ask when singletons are appropriate — good for shared configuration but can make testing harder. Favor dependency injection for testability.
class Event:
def __init__(self):
self._listeners = []
def subscribe(self, fn):
self._listeners.append(fn)
def emit(self, *args, **kwargs):
for fn in list(self._listeners):
fn(*args, **kwargs)
# Example usage
# evt = Event()
# evt.subscribe(lambda msg: print('got', msg))
# evt.emit('hello')
class StrategyA:
def do(self, data):
return f'A processed {data}'
class StrategyB:
def do(self, data):
return f'B processed {data}'
class Context:
def __init__(self, strategy):
self._strategy = strategy
def run(self, data):
return self._strategy.do(data)
# context = Context(StrategyA())
# context.run('input')
# Example: SRP — separate logger from processing
class Processor:
def __init__(self, logger):
self.logger = logger
def process(self, data):
self.logger.log('processing')
return data.upper()
Be able to explain where you used them, why chosen, and trade-offs.
# pytest example
import pytest
def test_deposit_withdraw():
from datetime import datetime
acc = BankAccount('Test', 100)
acc.deposit(50)
assert acc.balance == 150
acc.withdraw(30)
assert acc.balance == 120
with pytest.raises(ValueError):
acc.withdraw(1000)
Examples interviewers ask to evaluate design thinking (not just code):
Approach: clarify requirements, define key classes/interfaces, show sequence of interactions, mention trade-offs (complexity, testing, performance).
__slots__
and avoid setters, or return frozen dataclasses: @dataclass(frozen=True)
. For deep immutability, ensure contained objects are immutable or copied.None
and set inside: def __init__(self, lst=None): self.lst = lst or []
.LRUCache
class with get
and put
methods (use OrderedDict or doubly-linked list + dict).