Low-Level Design: Parking Lot System — Space Management, Ticketing, and Dynamic Pricing

Core Entities

ParkingLot: lot_id, name, address, total_floors, total_spaces, open_time, close_time, timezone. ParkingFloor: floor_id, lot_id, floor_number, total_spaces, available_spaces. ParkingSpace: space_id, floor_id, space_number, type (COMPACT, STANDARD, LARGE, HANDICAPPED, EV_CHARGING, MOTORCYCLE), status (AVAILABLE, OCCUPIED, RESERVED, MAINTENANCE), is_active. Vehicle: vehicle_id, license_plate, type (MOTORCYCLE, COMPACT, STANDARD, SUV, TRUCK), owner_id. ParkingTicket: ticket_id, space_id, vehicle_id, entry_time, exit_time, duration_minutes, base_fee, dynamic_multiplier, total_fee, payment_status (PENDING, PAID, EXEMPTED), payment_method. Reservation: reservation_id, space_id, vehicle_id, reserved_from, reserved_until, status (ACTIVE, USED, EXPIRED, CANCELLED), fee_paid. PricingRule: rule_id, lot_id, vehicle_type, hour_start, hour_end, day_type (WEEKDAY, WEEKEND, HOLIDAY), rate_per_hour, minimum_fee.

Entry and Exit Flow

class ParkingService:
    def vehicle_entry(self, lot_id: int, license_plate: str,
                      vehicle_type: str) -> ParkingTicket:
        with self.db.transaction():
            # Check for pre-existing reservation
            reservation = self.repo.get_active_reservation(
                lot_id, license_plate, datetime.utcnow()
            )
            if reservation:
                space = self.repo.get_space(reservation.space_id)
                reservation.status = ReservationStatus.USED
            else:
                # Find best available space
                space = self.repo.find_available_space(lot_id, vehicle_type)
                if not space:
                    raise LotFullError(f'No {vehicle_type} spaces available')

            space.status = SpaceStatus.OCCUPIED
            self.db.execute(
                'UPDATE parking_floors SET available_spaces = available_spaces - 1 ' +
                'WHERE floor_id = :f', f=space.floor_id
            )
            ticket = ParkingTicket(
                space_id=space.space_id,
                vehicle_id=self._get_or_create_vehicle(license_plate, vehicle_type).vehicle_id,
                entry_time=datetime.utcnow(),
                payment_status=PaymentStatus.PENDING
            )
            self.repo.save(ticket)
            return ticket

    def vehicle_exit(self, ticket_id: int) -> ParkingTicket:
        with self.db.transaction():
            ticket = self.repo.get_ticket(ticket_id, lock=True)
            if ticket.exit_time:
                raise AlreadyExitedError()
            ticket.exit_time = datetime.utcnow()
            ticket.duration_minutes = int(
                (ticket.exit_time - ticket.entry_time).total_seconds() / 60
            )
            ticket.total_fee = self._calculate_fee(ticket)
            space = self.repo.get_space(ticket.space_id)
            space.status = SpaceStatus.AVAILABLE
            self.db.execute(
                'UPDATE parking_floors SET available_spaces = available_spaces + 1 ' +
                'WHERE floor_id = :f', f=space.floor_id
            )
            return ticket

Space Assignment Strategy

When assigning spaces to vehicles without a reservation, use a strategy that optimizes both user convenience and lot utilization. Assignment rules: vehicle type matching: motorcycles get motorcycle spaces first; if full, assign compact. Compact cars get compact or standard. SUVs and trucks require large spaces. EV vehicles get EV charging spaces if available, otherwise standard. Floor preference: minimize walking distance — assign the lowest floor with available spaces of the required type. Within a floor: assign spaces closest to the exit ramp (smaller space number = closer to ramp, stored as a property of each space). VIP/premium spaces (first 5 spaces on floor 1): reserved for users who have paid a premium or loyalty members. Space index: maintain a Redis sorted set per (lot_id, floor_id, vehicle_type) with available space_ids. ZPOPMIN to get and claim the best space atomically. Update Redis and database on each entry/exit. Handicapped spaces: never auto-assign to regular vehicles. Only assignable with valid handicap registration.

Dynamic Pricing and Fee Calculation

Base rate: fetched from PricingRule based on vehicle type, current time, and day type. The rate is the cost per hour. Dynamic multiplier: adjust the base rate based on current lot occupancy. Occupancy 0-50%: 1.0x (base rate). Occupancy 50-75%: 1.2x. Occupancy 75-90%: 1.5x. Occupancy > 90%: 2.0x. Multiplier is computed at entry time and locked in for the stay. Fee calculation: divide the stay into hourly blocks. For each block, apply the base rate for that hour (rates differ by time of day, e.g., peak hours 8-10am and 5-7pm cost more). Round up partial hours (1h 5m billed as 2 hours). Apply minimum fee (e.g., first 15 minutes free; minimum $2 after). Apply the dynamic multiplier to the total. Monthly passes: flat monthly fee, no per-day charges. Stored as a valid_pass flag on the vehicle. Entry validation checks for valid pass before calculating fees. Validation (grace period): certain businesses validate parking — the merchant’s validation code extends a time window (e.g., 2 hours free). Applied as a discount on exit.

Reservations and Real-Time Availability

Advance reservations: users can reserve a specific space up to 30 days in advance. On reservation creation: decrement available_spaces for that time window. On reservation expiry (vehicle doesn’t arrive within 15 minutes of reserved_from): release the space, refund 50%. Real-time availability API: GET /lots/{id}/availability returns: total spaces, available by type, floors with availability. Response is cached in Redis for 10 seconds (updates frequently). WebSocket: live availability pushed to apps as spaces change. Digital signage: lot entrance boards display available space counts per type. Populated by the same API. Overcapacity prevention: available_spaces on ParkingFloor is updated atomically in the same transaction as space status changes. No separate counter can drift out of sync with actual space statuses. Periodic reconciliation job (hourly): counts actual AVAILABLE spaces per floor, compares to the counter, corrects any discrepancy (from bugs, crashes), logs the delta.

See also: Shopify Interview Prep

See also: Stripe Interview Prep

See also: Uber Interview Prep

See also: Airbnb Interview Guide 2026: Search Systems, Trust and Safety, and Full-Stack Engineering

See also: Scale AI Interview Guide 2026: Data Infrastructure, RLHF Pipelines, and ML Engineering

See also: Netflix Interview Guide 2026: Streaming Architecture, Recommendation Systems, and Engineering Excellence

Scroll to Top