LLD Case Study: Designing a Parking Lot

October 17, 2026 · 4 min read

"Design a parking lot" is asked in low-level design interviews more than any other problem. It's small enough to finish in 45 minutes, and it tests most of what interviewers look for: identifying entities, assigning responsibilities, choosing relationships, and leaving the right things easy to change.

1. Clarify the requirements

Ask before designing. A reasonable scope:

  • Several levels, each with spots of three sizes: small (motorcycle), medium (car), large (bus/truck).
  • A vehicle fits in a spot of its size or larger.
  • On entry, assign a spot and issue a ticket; on exit, charge a fee by time parked, then free the spot.
  • Show free spots per level and size.
  • Pricing rules will change (hourly, weekend flat rate, first hour free).

Out of scope, said out loud: reservations, payment processing details, license plate recognition, multiple lots.

2. Find the entities

Underline the nouns: lot, level, spot, vehicle, ticket, gate, fee. Then decide what each is responsible for:

classresponsibility
ParkingLotentry point: park(vehicle) → Ticket, leave(ticket) → fee
Levelowns its spots; reports availability
ParkingSpotsize and state (FREE / OCCUPIED); atomic reserve/release
Vehicleplate and size
Ticketvehicle, spot, entry time, status
SpotAllocatorinterface: choose a spot for a vehicle
PricingPolicyinterface: compute the fee for a ticket

3. Choose relationships deliberately

  • ParkingLot composes Levels, which compose ParkingSpots: spots don't exist without their level.
  • Vehicle types: a Car extends Vehicle hierarchy is the reflex, but the only thing that differs is size. A size field — an enum — is simpler, and adding an electric car later doesn't need a subclass. This is composition over inheritance in practice. (If vehicle types later behave differently — EVs needing charging spots — revisit.)
  • SpotAllocator and PricingPolicy are interfaces: the two things the requirements say will vary. That's the Strategy pattern.

4. The core flow

entry gate
ParkingLot
SpotAllocator
Level 2
Ticket
park(car KA-01-1234)
▶
classes
ParkingLot → Level → ParkingSpot Vehicle: size (SMALL | MEDIUM | LARGE) SpotAllocator, PricingPolicy (interfaces)

A car arrives at the entry gate. The gate knows nothing about levels or spots — it asks the parking lot.

0 / 6
enum Size { SMALL = 0, MEDIUM = 1, LARGE = 2 }

class ParkingSpot {
  private occupied = false
  constructor(readonly id: string, readonly size: Size) {}
  fits(v: Vehicle) { return this.size >= v.size }
  tryReserve(): boolean {            // must be atomic — see below
    if (this.occupied) return false
    this.occupied = true
    return true
  }
  release() { this.occupied = false }
}

interface SpotAllocator {
  candidates(levels: Level[], v: Vehicle): Iterable<ParkingSpot>
}

interface PricingPolicy {
  fee(ticket: Ticket, exit: Date): number
}

class ParkingLot {
  private active = new Map<string, Ticket>()
  constructor(
    private levels: Level[],
    private allocator: SpotAllocator,
    private pricing: PricingPolicy,
  ) {}

  park(v: Vehicle, now = new Date()): Ticket {
    for (const spot of this.allocator.candidates(this.levels, v)) {
      if (spot.fits(v) && spot.tryReserve()) {
        const ticket = new Ticket(crypto.randomUUID(), v, spot, now)
        this.active.set(ticket.id, ticket)
        return ticket
      }
    }
    throw new LotFullError(v.size)
  }

  leave(ticketId: string, now = new Date()): number {
    const ticket = this.active.get(ticketId)
    if (!ticket) throw new UnknownTicketError(ticketId)
    const fee = this.pricing.fee(ticket, now)
    ticket.spot.release()
    this.active.delete(ticketId)
    return fee
  }
}

Notice what ParkingLot doesn't know: which spot is "best", or how fees are computed. Both come in through the constructor — plain dependency injection — so tests can pass a fixed allocator and a fake clock.

5. Concurrency

Two cars arriving at two gates can pick the same free spot. The check ("is it free?") and the update ("mark it occupied") must be one atomic step:

  • In memory, multi-threaded (Java, Go): compare-and-set on an atomic flag, or a lock per level.
  • With a database: a conditional update — UPDATE spots SET occupied = true WHERE id = ? AND occupied = false — and check that one row changed. If not, try the next candidate.

The allocator's candidates are just suggestions. tryReserve is the source of truth. Mentioning this race unprompted is one of the clearest signals of a strong answer.

6. Extensions to discuss

  • New pricing: weekend flat rate, first hour free, a cap per day — each a new PricingPolicy, or a decorator around an existing one.
  • EV charging spots: a capability on the spot, and an allocator that prefers them for EVs.
  • Availability display: keep per-level counters updated on reserve and release, or publish events that a display board observes.
  • Lost ticket: look up by plate, charge a maximum fee.

Practise it

This is the first problem in the LLD practice tool. Write your notes, draw the class diagram — ParkingLot composing Levels, the strategies as interfaces — and get it reviewed by Claude against the same rubric used here.