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