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

Source: https://www.techinterview.org/post/3233460514/object-oriented-design-interview-questions/
Updated: 2026-07-05 · techinterview.org

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 < self.copies_total:
            self.copies_available += 1

    @property
    def status(self) -> 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) < self.max_books and self.fines_due == 0

@dataclass
class Loan:
    loan_id: str
    isbn: str
    member_id: str
    checkout_date: datetime
    due_date: datetime
    return_date: Optional[datetime] = None

    LOAN_PERIOD_DAYS = 14
    FINE_PER_DAY = 0.25

    @classmethod
    def create(cls, isbn: str, member_id: str) -> "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 <= self.due_date:
            return 0.0
        days_late = (end - self.due_date).days
        return days_late * self.FINE_PER_DAY


class Library:
    def __init__(self):
        self._books: dict[str, Book] = {}
        self._members: dict[str, Member] = {}
        self._loans: dict[str, Loan] = {}  # loan_id -> 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.
