System Design Guidelines — OOSD Case Study: Multi-Level Parking Lot

Topic: Design a Multi-Level Parking Lot using Object-Oriented System Design (OOSD) principles
Asked in interviews at: Amazon, Apple, Google, etc.


Design Guidelines (What we consider in this exercise)

  1. Ignore UI, persistence, and networking. Focus on objects, responsibilities and relationships.
  2. Design in-memory data structures for storing and retrieving state.
  3. Classes, interfaces, properties and behaviors matter most.
  4. Use UML-style thinking to explain relationships (association, aggregation, composition).

Problem & Assumptions

We adopt the following assumptions to keep the problem tractable while still interesting:

  1. The parking lot has multiple levels; each level has multiple rows and spots.
  2. The lot must support motorcycles, cars and buses.
  3. Spots are of three logical sizes: Motorcycle, Compact, Large.
  4. A motorcycle can park in any spot.
  5. A car fits in a compact or large spot.
  6. A bus requires 5 consecutive large spots in the same row.
  7. To reduce complexity and fragmentation, we model spot sizes rather than separate classes per spot type.

High-level Design (OOSD concepts)

  • Use an abstract Vehicle class with concrete subclasses: Motorcycle, Car, Bus.
  • Use a ParkingSpot class that has a VehicleSize enum rather than separate subclasses for each spot type.
  • Level manages its rows and spots. ParkingLot manages multiple Levels.
  • Vehicles know how many spotsNeeded they require and what VehicleSize they are.
  • Vehicles keep track of the ParkingSpots in which they are parked (aggregation).
  • Spot ownership of a Vehicle is composition-like — when a spot is occupied that vehicle is referenced by the spot.

UML relationships (conceptual)

  • Aggregation: Level aggregates ParkingSpots (a level can exist without any vehicles).
  • Composition: ParkingLot composes Levels (levels are integral parts of the lot).
  • Association: VehicleParkingSpot (a vehicle is associated with the spots it occupies).

(You can draw a class diagram with ParkingLotLevelParkingSpot and Vehicle subclasses linking to ParkingSpot.)


Core Java Model (key classes & snippets)

1. VehicleSize enum

public enum VehicleSize {
    Motorcycle, Compact, Large
}

2. Vehicle (abstract)

public abstract class Vehicle {
    protected ArrayList<ParkingSpot> parkingSpots = new ArrayList<>();
    protected String licensePlate;
    protected int spotsNeeded;
    protected VehicleSize size;

    public int getSpotsNeeded() { return spotsNeeded; }
    public VehicleSize getSize() { return size; }

    // record that this vehicle is parked in spot s
    public void parkInSpot(ParkingSpot s) {
        parkingSpots.add(s);
    }

    // remove vehicle from all spots (implementation omitted)
    public void clearSpots() { /* ... */ }

    // can this vehicle fit in a particular spot? (size check only)
    public abstract boolean canFitInSpot(ParkingSpot spot);
}

3. Concrete vehicles

public class Bus extends Vehicle {
    public Bus() {
        spotsNeeded = 5;
        size = VehicleSize.Large;
    }
    public boolean canFitInSpot(ParkingSpot spot) {
        return spot.getSpotSize() == VehicleSize.Large;
    }
}

public class Car extends Vehicle {
    public Car() {
        spotsNeeded = 1;
        size = VehicleSize.Compact;
    }
    public boolean canFitInSpot(ParkingSpot spot) {
        return spot.getSpotSize() == VehicleSize.Compact ||
               spot.getSpotSize() == VehicleSize.Large;
    }
}

public class Motorcycle extends Vehicle {
    public Motorcycle() {
        spotsNeeded = 1;
        size = VehicleSize.Motorcycle;
    }
    public boolean canFitInSpot(ParkingSpot spot) {
        // motorcycle can park anywhere
        return true;
    }
}

4. ParkingSpot

public class ParkingSpot {
    private Vehicle vehicle;
    private VehicleSize spotSize;
    private int row;
    private int spotNumber;
    private Level level;

    public ParkingSpot(Level lvl, int r, int n, VehicleSize s) {
        level = lvl; row = r; spotNumber = n; spotSize = s;
    }

    public boolean isAvailable() {
        return vehicle == null;
    }

    public VehicleSize getSpotSize() { return spotSize; }

    // Check if spot is big enough and available
    public boolean canFitVehicle(Vehicle vehicle) {
        if (!isAvailable()) return false;
        // the Vehicle class handles size semantics (or we can centralize here)
        if (vehicle.getSize() == VehicleSize.Motorcycle) return true;
        if (vehicle.getSize() == VehicleSize.Compact)
            return spotSize == VehicleSize.Compact || spotSize == VehicleSize.Large;
        return spotSize == VehicleSize.Large; // for Bus individual spot check
    }

    // Park vehicle in this spot
    public boolean park(Vehicle v) {
        this.vehicle = v;
        v.parkInSpot(this);
        return true;
    }

    // Remove vehicle and notify level
    public void removeVehicle() {
        this.vehicle = null;
        // optionally inform level of availability
    }

    public int getRow() { return row; }
    public int getSpotNumber() { return spotNumber; }
}

Parking algorithm notes

  • Small vehicles (motorcycle, car): find first available spot that canFitVehicle() returns true and park.
  • Bus: must find 5 consecutive available Large spots in the same row. The Level class should implement a method to scan rows for such a block.
  • When you park a vehicle, mark all ParkingSpots occupied and let the Vehicle record the spots.
  • When removing a vehicle, Vehicle.clearSpots() iterates its parkingSpots and calls removeVehicle() on each spot.

Level & ParkingLot (brief)

  • Level holds a 2D structure (rows × spots) and provides:
    • parkVehicle(Vehicle v) which decides how/where to park
    • findAvailableSpots(Vehicle v) which returns suitable spot(s)
    • spotFreed() callback for bookkeeping
  • ParkingLot contains multiple Level objects and:
    • iterates through levels to find available space
    • provides global operations (status, total free spots, reports)

Design tradeoffs & decisions

  • Single ParkingSpot class vs many subclasses: We favored a single ParkingSpot with spotSize to reduce code duplication. Use subclasses only if spots have different behaviors.
  • Bus parking: requires block search. This complicates allocation but avoids fragmentation if implemented carefully.
  • Threading & concurrency: Real system would need locks or synchronization around spot allocation (not covered here).
  • Persistence: Not part of this exercise — but in a real system you’d persist Level/Spot/Vehicle state to DB and restore on startup.

What to present in an interview

  1. Start with assumptions (what is in scope / out of scope).
  2. Sketch UML showing classes and relationships (ParkingLot → Level → ParkingSpot; Vehicle hierarchy).
  3. Explain the parking algorithm for each vehicle type and how you search for spots.
  4. Show key code snippets (Vehicle, ParkingSpot, Bus logic).
  5. Mention concurrency, persistence, and scaling concerns and how you’d address them (locks, DB transactions, cache, distributed coordination).

Summary

This design models a flexible multi-level parking lot using core OOSD principles:

  • clear abstractions (Vehicle, ParkingSpot, Level, ParkingLot)
  • polymorphism for vehicle behaviors
  • aggregation/composition to show ownership and lifetime
  • a straightforward algorithmic approach for special cases like buses.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top