Skip to content

Parking / Slot Allocation — Designed in Stages

First PublishedByAtif Alam

You don’t need to design for scale on day one.

Define what you need—lots, zones, slots, sessions, and no double-assignment—then build the simplest thing that works and evolve as you add multiple lots, sensors, reservations, or payment at exit.

Here we use a parking lot or slot-allocation system as the running example: finding and assigning slots when a vehicle enters, freeing them on exit, and optionally taking payment. The same staged thinking applies to any system that allocates finite resources (slots, bays, spaces) with clear enter/exit or start/end semantics.

Requirements and Constraints (no architecture yet)

Section titled “Requirements and Constraints (no architecture yet)”

Functional Requirements

  • Lot — a parking facility; may contain zones (e.g. levels, areas) and many slots.
  • Zone — optional grouping of slots (e.g. by floor, type such as compact or accessible).
  • Slot — a single parkable space; has an identity, optional zone/type, and status (available | occupied).
  • Vehicle / Session — a visit: enter time, exit time (or ongoing), slot assigned; optional payment at exit (see Payments example for payment design).

Quality Requirements

  • No double-assignment — a slot must never be assigned to two active sessions at once; allocation and release must be consistent.
  • Availability accuracy — counts and lists of available slots should reflect reality (or converge quickly if updated asynchronously).
  • Optional payment — if you charge at exit, integrate with a payments flow (idempotency, audit); payment design is covered in the Payments example.

Key Entities

  • Lot — identifier, name, optional metadata.
  • Zone — identifier, lot_id, name or type (e.g. Level 1, Compact).
  • Slot — id, zone_id (or lot_id), type optional, status (available | occupied), optional attributes (e.g. EV, accessible).
  • Session — id, slot_id, vehicle identifier (plate or token), entered_at, exited_at (null if active); optional payment_id.

Primary Use Cases and Access Patterns

  • Find available slots — by lot, zone, or type; read-heavy; may be filtered (e.g. EV only, accessible).
  • Enter — assign an available slot to a vehicle; create session; mark slot occupied; must be atomic to avoid double-assignment.
  • Exit — end session, free slot; optional compute duration and trigger payment (integrate with Payments example).
  • Optional: Reserve — book a slot in advance for a time window (Stage 3).

Given this, start with the simplest MVP: one API, one DB, slots and sessions, atomic enter/exit, single lot—then add multi-zone/multi-lot, sensors or manual updates, availability filters, and optional payment; finally reservations and dynamic pricing if needed.

Stage 1 — MVP (simple, correct, not over-engineered)

Section titled “Stage 1 — MVP (simple, correct, not over-engineered)”

Goal

Ship a correct slot-allocation core: enter assigns one available slot and starts a session; exit frees the slot and ends the session. One API, one DB, single lot, no double-assignment.

Components

  • API — REST or similar; auth if needed; endpoints: list available slots (by lot or zone), enter (optionally specify zone/type preference), exit (session id or vehicle id). Returns slot id on enter, session summary on exit.
  • Primary DB — stores lots, zones (optional), slots (id, zone_id, status), sessions (id, slot_id, vehicle_ref, entered_at, exited_at). Use DB transactions so that “allocate slot + create session” and “end session + free slot” are atomic.
  • Slot allocation — on enter: in one transaction, select an available slot (e.g. SELECT ... FOR UPDATE or equivalent), insert session, update slot to occupied. On exit: in one transaction, update session with exited_at, update slot to available.

Minimal Diagram

Client (driver / kiosk)
|
v
+-----------------+
| API |
+-----------------+
|
v
Primary DB (single node)
- lots
- zones (optional)
- slots (id, zone_id, status: available | occupied)
- sessions (id, slot_id, vehicle_ref, entered_at, exited_at)
Enter: BEGIN; lock slot row; insert session; set slot occupied; COMMIT
Exit: BEGIN; update session; set slot available; COMMIT

Patterns and Concerns (don’t overbuild)

  • Single writer or row-level locking so two concurrent “enter” requests cannot assign the same slot; use DB transactions and SELECT ... FOR UPDATE (or equivalent) when picking a slot.
  • Idempotency for enter (optional but useful): if client retries “enter” for same vehicle/session, either return existing session or enforce one active session per vehicle; avoid assigning a second slot.
  • Validation: slot exists, slot is available on enter; session exists and is active on exit.

Why This Is a Correct MVP

  • One API, one DB, atomic enter/exit → no double-assignment, clear state, easy to reason about.
  • Single lot and in-DB availability are enough to validate the model; multi-lot, sensors, and payment can wait until you have real demand for them.

Stage 2 — Growth Phase (multi-zone, multi-lot, availability filters, optional payment)

Section titled “Stage 2 — Growth Phase (multi-zone, multi-lot, availability filters, optional payment)”

What Triggers the Growth Phase?

  • You add more lots or zones; you need availability queries filtered by zone, type, or lot.
  • Slot status is updated by sensors (or manual ops) as well as by enter/exit; you want consistent status and maybe faster availability reads.
  • You introduce payment at exit and need to integrate with a payments flow without blocking exit.

Components to Add (incrementally)

  • Multi-zone or multi-lot — same DB schema; slots belong to zone/lot; API accepts lot_id and optional zone_id or type filters for “list available slots.”
  • Sensors or manual status update — optional: sensors (or admin) can set slot status (e.g. available/occupied/blocked); reconcile with session state (e.g. session says occupied but sensor says empty → alert or auto-close session). Prefer single source of truth (sessions) and treat sensor as input to that, or document reconciliation rules.
  • Availability query with filters — index slots by (lot_id, zone_id, status, type); cache hot queries (e.g. “available count per lot”) with short TTL if needed; ensure filters and counts are consistent with allocation logic.
  • Optional payment at exit — on exit, compute duration, call payments API (idempotent) for the session; see Payments example for idempotency keys and audit. Exit flow: end session and free slot in DB, then trigger payment (sync or async); do not block slot release on payment success if you allow “pay later” or retry.

Growth Diagram

+------------------+
Clients ----------> | Load Balancer |
+------------------+
|
v
+-----------------+
| API |
+-----------------+
|
+-----------------+-----------------+
| | |
v v v
List available Enter (assign) Exit (free + optional pay)
(by lot/zone/type) (transaction) (transaction + payment call)
| | |
v v v
Primary DB (lots, zones, slots, sessions)
^ |
| v
Optional: sensor/ops Queue (optional: async payment side effects)
status updates Workers (notify, receipt)

Patterns and Concerns to Introduce (practical scaling)

  • Consistency of availability: either derive availability from sessions (slot free iff no active session) or maintain status with clear reconciliation with sessions; avoid two conflicting sources without a resolution strategy.
  • Payment at exit: use idempotency key (e.g. session_id) when calling payments; keep exit and slot release in one transaction; payment can be async after that.
  • Monitoring: allocation latency, failed allocations (no slot available), exit flow errors, payment failures.

Still Avoid (common over-engineering here)

  • Distributed transactions across “slot service” and “payment service” before you have separate services.
  • Real-time reservation and dynamic pricing before you have proven demand and a clear product model.

Stage 3 — Advanced Scale (reservations, dynamic pricing, real-time occupancy)

Section titled “Stage 3 — Advanced Scale (reservations, dynamic pricing, real-time occupancy)”

What Triggers Advanced Scale?

  • You need to reserve a slot in advance (e.g. book for a time window); allocation is no longer only on enter.
  • You introduce dynamic pricing (e.g. by time of day, occupancy); pricing may depend on real-time or near-real-time occupancy.
  • Many lots and high concurrency; availability and occupancy data need to scale (read replicas, caching, or dedicated occupancy view).

Components (common advanced additions)

  • Reservation — reserve a slot for a time window (start, end); on enter, match vehicle to reservation (e.g. by token or plate) and convert to session; release reservation if no-show after window. Slots have two dimensions: reserved vs not, and occupied vs available; allocation logic must respect reservations (e.g. only offer unreserved or reserved-for-you slots).
  • Dynamic pricing — pricing service or table keyed by lot, time, occupancy band; at exit (or on reservation) compute price and call payments; optionally cache occupancy aggregates for pricing inputs.
  • Scale and real-time occupancy — read replicas for availability and list queries; cache per-lot or per-zone counts with short TTL; optional event stream (slot freed/occupied) for dashboards or pricing; ensure reservation and enter still use primary or serialized writes to avoid double-assignment.

Advanced Diagram (conceptual)

+------------------+
Clients ----------> | API Gateway / LB |
+------------------+
|
+-----------------+-----------------+
v v v
Availability Enter / Exit Reservations
(list, count) (allocate, free) (book, no-show release)
| | |
v v v
Read Replicas Primary DB Primary DB
(slots, sessions) (slots, sessions, (reservations)
reservations)
| | |
v v v
Cache (counts, Payment service Optional: event stream
per lot/zone) (idempotent) (occupancy for pricing/dashboards)

Patterns and Concerns at This Stage

  • Reservation vs walk-in: clear state machine (reserved → session when enter, or expired/no-show); only one active session per slot; reservations that overlap in time must map to distinct slots.
  • Dynamic pricing: use occupancy and time as inputs; keep pricing read path separate from allocation write path so allocation stays consistent.
  • Scale: shard by lot_id if needed; keep enter/exit and reservation allocation on primary or within one partition; availability reads can use replicas and cache.

MVP delivers correct slot allocation with one API, one DB, atomic enter and exit, and a single lot. No double-assignment, clear session and slot state.

As you grow, you add multi-zone and multi-lot, availability filters, optional sensor or manual status updates, and payment at exit (integrated with the Payments example). You keep allocation and slot release in a single transactional boundary and avoid distributing that before you have multiple services.

At advanced scale, you add reservations (book in advance), dynamic pricing driven by occupancy and time, and scaling for availability and occupancy reads (replicas, cache, optional event stream). Allocation and reservation logic stay consistent; complexity is added only where product and scale require it.

This approach gives you:

  • Start Simple — one API, one DB, slots and sessions, atomic enter/exit; ship and learn.
  • Scale Intentionally — add lots and zones, then payment and sensors; add reservations and pricing when the product demands them.
  • Add Complexity Only When Required — keep no double-assignment and availability accuracy as non-negotiables; add reservation and pricing without breaking the core allocation model.