Object-Oriented Design Interview Questions: Parking Lot, Library, and More

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

  1. Clarify requirements: What features are in scope? Edge cases?
  2. Identify entities (nouns): These become your classes
  3. Identify behaviors (verbs): These become your methods
  4. Define relationships: Inheritance vs composition vs association
  5. Apply SOLID principles: Single Responsibility, Open/Closed, etc.
  6. 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.”
}
}
]
}

Scroll to Top