Object-oriented design (OOD) questions test whether you can translate real-world requirements into clean class hierarchies. Amazon, Uber, and Microsoft frequently ask these as part of the design interview. The key is to identify entities, relationships, and behaviors — then use design principles (SOLID, composition over inheritance) to structure the code.
OOD Interview Framework
- Clarify requirements: What features are in scope? Edge cases?
- Identify entities (nouns): These become your classes
- Identify behaviors (verbs): These become your methods
- Define relationships: Inheritance vs composition vs association
- Apply SOLID principles: Single Responsibility, Open/Closed, etc.
- Handle concurrency: Thread safety where needed
Problem 1: Design a Parking Lot System
Requirements
- Multiple levels, each with multiple spots
- Spot types: motorcycle, compact, large
- Vehicles: motorcycle, car, truck (each fits in different spot types)
- Track which spots are available
- Calculate parking fee based on duration and vehicle type
from enum import Enum, auto
from datetime import datetime
from typing import Optional
import threading
class SpotType(Enum):
MOTORCYCLE = auto()
COMPACT = auto()
LARGE = auto()
class VehicleType(Enum):
MOTORCYCLE = auto()
CAR = auto()
TRUCK = auto()
# Strategy: which spot types each vehicle can use
VEHICLE_SPOT_COMPAT = {
VehicleType.MOTORCYCLE: [SpotType.MOTORCYCLE, SpotType.COMPACT, SpotType.LARGE],
VehicleType.CAR: [SpotType.COMPACT, SpotType.LARGE],
VehicleType.TRUCK: [SpotType.LARGE],
}
HOURLY_RATES = {
VehicleType.MOTORCYCLE: 1.0,
VehicleType.CAR: 2.0,
VehicleType.TRUCK: 3.5,
}
class Vehicle:
def __init__(self, license_plate: str, vehicle_type: VehicleType):
self.license_plate = license_plate
self.vehicle_type = vehicle_type
class ParkingSpot:
def __init__(self, spot_id: str, spot_type: SpotType, level: int):
self.spot_id = spot_id
self.spot_type = spot_type
self.level = level
self._vehicle: Optional[Vehicle] = None
self._parked_at: Optional[datetime] = None
self._lock = threading.Lock()
def is_available(self) -> bool:
return self._vehicle is None
def can_fit(self, vehicle: Vehicle) -> bool:
return (self.is_available() and
self.spot_type in VEHICLE_SPOT_COMPAT[vehicle.vehicle_type])
def park(self, vehicle: Vehicle) -> bool:
with self._lock:
if not self.can_fit(vehicle):
return False
self._vehicle = vehicle
self._parked_at = datetime.now()
return True
def remove(self) -> Optional[float]:
"""Returns fee in dollars."""
with self._lock:
if not self._vehicle:
return None
duration_hours = (datetime.now() - self._parked_at).total_seconds() / 3600
fee = max(1.0, duration_hours) * HOURLY_RATES[self._vehicle.vehicle_type]
self._vehicle = None
self._parked_at = None
return round(fee, 2)
class ParkingLevel:
def __init__(self, level: int, spots_config: dict[SpotType, int]):
self.level = level
self.spots: list[ParkingSpot] = []
spot_num = 1
for spot_type, count in spots_config.items():
for _ in range(count):
self.spots.append(
ParkingSpot(f"L{level}-{spot_num:03d}", spot_type, level)
)
spot_num += 1
def find_spot(self, vehicle: Vehicle) -> Optional[ParkingSpot]:
for spot in self.spots:
if spot.can_fit(vehicle):
return spot
return None
def available_count(self, spot_type: Optional[SpotType] = None) -> int:
return sum(1 for s in self.spots
if s.is_available() and (spot_type is None or s.spot_type == spot_type))
class ParkingLot:
def __init__(self, levels: list[ParkingLevel]):
self.levels = levels
self._tickets: dict[str, ParkingSpot] = {} # license_plate -> spot
self._lock = threading.Lock()
def park(self, vehicle: Vehicle) -> Optional[str]:
with self._lock:
if vehicle.license_plate in self._tickets:
return None # already parked
for level in self.levels:
spot = level.find_spot(vehicle)
if spot and spot.park(vehicle):
self._tickets[vehicle.license_plate] = spot
return spot.spot_id # return spot ID as ticket
return None # lot is full
def leave(self, license_plate: str) -> Optional[float]:
with self._lock:
spot = self._tickets.pop(license_plate, None)
if not spot:
return None
return spot.remove()
# Usage
lot = ParkingLot([
ParkingLevel(1, {SpotType.MOTORCYCLE: 10, SpotType.COMPACT: 30, SpotType.LARGE: 10}),
ParkingLevel(2, {SpotType.COMPACT: 40, SpotType.LARGE: 10}),
])
car = Vehicle("ABC-123", VehicleType.CAR)
ticket = lot.park(car) # "L1-011" (first compact spot)
fee = lot.leave("ABC-123") # fee in dollars
Problem 2: Library Management System
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from typing import Optional
from enum import Enum, auto
class BookStatus(Enum):
AVAILABLE = auto()
CHECKED_OUT = auto()
RESERVED = auto()
@dataclass
class Book:
isbn: str
title: str
author: str
copies_total: int
copies_available: int = field(init=False)
def __post_init__(self):
self.copies_available = self.copies_total
def checkout(self) -> bool:
if self.copies_available > 0:
self.copies_available -= 1
return True
return False
def return_book(self) -> None:
if self.copies_available BookStatus:
if self.copies_available > 0:
return BookStatus.AVAILABLE
return BookStatus.CHECKED_OUT
@dataclass
class Member:
member_id: str
name: str
email: str
max_books: int = 5
checked_out: list[str] = field(default_factory=list) # list of ISBNs
fines_due: float = 0.0
def can_checkout(self) -> bool:
return len(self.checked_out) "Loan":
now = datetime.now()
return cls(
loan_id=f"{isbn}:{member_id}:{int(now.timestamp())}",
isbn=isbn,
member_id=member_id,
checkout_date=now,
due_date=now + timedelta(days=cls.LOAN_PERIOD_DAYS),
)
def compute_fine(self) -> float:
end = self.return_date or datetime.now()
if end Loan
self._active_loans: dict[tuple, str] = {} # (isbn, member_id) -> loan_id
def add_book(self, book: Book) -> None:
self._books[book.isbn] = book
def register_member(self, member: Member) -> None:
self._members[member.member_id] = member
def checkout(self, isbn: str, member_id: str) -> Optional[str]:
book = self._books.get(isbn)
member = self._members.get(member_id)
if not book or not member:
return None
if not book.checkout() or not member.can_checkout():
return None
if (isbn, member_id) in self._active_loans:
return None # already borrowed this book
loan = Loan.create(isbn, member_id)
self._loans[loan.loan_id] = loan
self._active_loans[(isbn, member_id)] = loan.loan_id
member.checked_out.append(isbn)
return loan.loan_id
def return_book(self, isbn: str, member_id: str) -> float:
loan_id = self._active_loans.pop((isbn, member_id), None)
if not loan_id:
return 0.0
loan = self._loans[loan_id]
loan.return_date = datetime.now()
fine = loan.compute_fine()
book = self._books[isbn]
book.return_book()
member = self._members[member_id]
member.checked_out.remove(isbn)
member.fines_due += fine
return fine
def search(self, query: str) -> list[Book]:
q = query.lower()
return [b for b in self._books.values()
if q in b.title.lower() or q in b.author.lower()]
SOLID Principles Applied
| Principle | Example from Parking Lot |
|---|---|
| Single Responsibility | ParkingSpot handles spot state; ParkingLot handles allocation; no class does both |
| Open/Closed | Add new vehicle types without modifying ParkingSpot — extend VEHICLE_SPOT_COMPAT dict |
| Liskov Substitution | Any Vehicle subtype works anywhere Vehicle is expected |
| Interface Segregation | Spot, Level, and Lot have focused interfaces — callers only see what they need |
| Dependency Inversion | ParkingLot depends on ParkingLevel abstraction, not on specific level implementation |
Common OOD Interview Questions
- Elevator System: Elevator, ElevatorController, Floor, Request classes. Key: scheduling algorithm (SCAN/LOOK), handling concurrent requests from multiple floors
- Chess Game: Board, Piece (King, Queen, Rook…), Move, Player, Game classes. Key: polymorphic
is_valid_move()per piece type, check detection - Online Bookstore: Book, Inventory, ShoppingCart, Order, Payment classes. Key: inventory reservation, payment processing abstraction
- Hotel Reservation: Room, RoomType, Reservation, Guest, Hotel classes. Key: availability checking, overbooking handling, cancellation policy
Frequently Asked Questions
How do you approach an object-oriented design interview question?
Use a structured framework: (1) Clarify requirements — ask about scale, features in scope, and edge cases before writing any code. (2) Identify entities (nouns) — these become your classes: ParkingLot, ParkingSpot, Vehicle, Level. (3) Identify behaviors (verbs) — these become methods: park(), remove(), find_available_spot(). (4) Define relationships — inheritance vs composition (prefer composition), has-a vs is-a, cardinality (one ParkingLot has many Levels, each Level has many ParkingSpots). (5) Apply SOLID principles — especially Single Responsibility (each class does one thing) and Open/Closed (extend without modifying). (6) Address concurrency — identify shared mutable state and add appropriate locking.
What is the difference between composition and inheritance in OOD?
Inheritance (is-a): a subclass extends a base class and inherits its methods. Use when there is a genuine type hierarchy (Dog is-a Animal). Avoid deep inheritance chains — changes to base classes break subclasses, and Java/Python only support single inheritance. Composition (has-a): a class holds a reference to another class and delegates behavior to it. More flexible — you can change behavior at runtime by swapping the composed object. Follows the Dependency Inversion Principle. The GoF design guideline "favor composition over inheritance" means: before creating a subclass, ask if the relationship is truly is-a or if has-a would be more appropriate. Most design patterns (Strategy, Decorator, Proxy) use composition.
What SOLID principle is most commonly violated and how do you fix it?
Single Responsibility Principle (SRP) is most commonly violated — one class does too many things. The symptom: a class has many reasons to change, or its methods span multiple concerns (e.g., a UserService that handles authentication, profile management, email sending, and analytics). Fix: split into UserAuthService, UserProfileService, EmailService. Another commonly violated principle is Open/Closed — code that must be modified to add new behavior (a large if/else or switch based on type). Fix: use the Strategy or Factory pattern to add new types without changing existing code. Liskov Substitution violations show up as subclasses that throw exceptions on inherited methods — this indicates the inheritance hierarchy is wrong.
{
“@context”: “https://schema.org”,
“@type”: “FAQPage”,
“mainEntity”: [
{
“@type”: “Question”,
“name”: “How do you approach an object-oriented design interview question?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Use a structured framework: (1) Clarify requirements — ask about scale, features in scope, and edge cases before writing any code. (2) Identify entities (nouns) — these become your classes: ParkingLot, ParkingSpot, Vehicle, Level. (3) Identify behaviors (verbs) — these become methods: park(), remove(), find_available_spot(). (4) Define relationships — inheritance vs composition (prefer composition), has-a vs is-a, cardinality (one ParkingLot has many Levels, each Level has many ParkingSpots). (5) Apply SOLID principles — especially Single Responsibility (each class does one thing) and Open/Closed (extend without modifying). (6) Address concurrency — identify shared mutable state and add appropriate locking.”
}
},
{
“@type”: “Question”,
“name”: “What is the difference between composition and inheritance in OOD?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Inheritance (is-a): a subclass extends a base class and inherits its methods. Use when there is a genuine type hierarchy (Dog is-a Animal). Avoid deep inheritance chains — changes to base classes break subclasses, and Java/Python only support single inheritance. Composition (has-a): a class holds a reference to another class and delegates behavior to it. More flexible — you can change behavior at runtime by swapping the composed object. Follows the Dependency Inversion Principle. The GoF design guideline “favor composition over inheritance” means: before creating a subclass, ask if the relationship is truly is-a or if has-a would be more appropriate. Most design patterns (Strategy, Decorator, Proxy) use composition.”
}
},
{
“@type”: “Question”,
“name”: “What SOLID principle is most commonly violated and how do you fix it?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Single Responsibility Principle (SRP) is most commonly violated — one class does too many things. The symptom: a class has many reasons to change, or its methods span multiple concerns (e.g., a UserService that handles authentication, profile management, email sending, and analytics). Fix: split into UserAuthService, UserProfileService, EmailService. Another commonly violated principle is Open/Closed — code that must be modified to add new behavior (a large if/else or switch based on type). Fix: use the Strategy or Factory pattern to add new types without changing existing code. Liskov Substitution violations show up as subclasses that throw exceptions on inherited methods — this indicates the inheritance hierarchy is wrong.”
}
}
]
}