SYSTEM_DESIGN

System Design: Bike/Scooter Sharing Platform

Design a dockless bike and scooter sharing system covering IoT vehicle management, lock/unlock mechanics, geofencing, dynamic pricing, and fleet rebalancing operations.

14 min readUpdated Jan 15, 2025
system-designmicromobilitybike-sharingscooteriotgeofencing

Requirements

Functional Requirements:

  • Users find nearby available bikes/scooters on a map and reserve for 15 minutes
  • Unlock vehicle via app QR scan or NFC tap; lock ends the trip and triggers billing
  • Real-time vehicle tracking for users and operations team
  • Geofencing: define ride zones, no-parking zones, speed limit zones
  • Dynamic pricing: surge during peak demand, discounts to incentivize rebalancing
  • Operations team manages fleet: rebalancing, maintenance flags, battery swaps

Non-Functional Requirements:

  • Lock/unlock command delivered to vehicle within 3 seconds
  • Vehicle location updated every 15 seconds when in motion, every 5 minutes when idle
  • Support 500,000 vehicles globally, 100,000 concurrent active rides
  • Geofence violation detection within 10 seconds of crossing boundary
  • 99.9% uptime for unlock path — failure means a stranded rider

Scale Estimation

500,000 vehicles; 100,000 active rides at peak. Active vehicles update every 15 seconds = 6,667 events/second. Idle vehicles (400,000) update every 5 minutes = 1,333 events/second. Total: ~8,000 location events/second. IoT events (battery level, lock state, alarm triggers): ~2,000/second. Trip events: 5 million rides/day = ~58 starts + 58 ends/second = 116 trip events/second. Reserve/search queries from user apps: 2 million queries/day peak in morning commute (~500/second).

High-Level Architecture

Vehicles (bikes/scooters) embed a cellular IoT module (SIM-enabled) running firmware that communicates with the cloud platform via MQTT over LTE. The platform's IoT layer ingests vehicle telemetry and commands, while the user-facing layer handles ride booking, map display, and payments.

Vehicles publish telemetry to an MQTT broker (AWS IoT Core) on topics keyed by vehicle_id. The broker forwards messages to Kinesis (or Kafka) for stream processing. A Vehicle State Processor updates vehicle state in DynamoDB (current status, battery, location) and geofence membership. Rides are managed by the Ride Service, which orchestrates: reserve → unlock → in-progress → locked → completed with payment via Stripe.

The ops dashboard consumes vehicle state from DynamoDB and displays a live heat map of fleet distribution, battery levels, and maintenance flags. Rebalancing tasks are created algorithmically by the Fleet Intelligence Service and pushed to field ops via a task management app.

Core Components

IoT Vehicle Gateway

Each vehicle runs a custom firmware that publishes to MQTT topics: telemetry/{vehicle_id}/location (lat, lng, speed, heading, battery_pct, lock_state) and telemetry/{vehicle_id}/events (alarm, impact, low_battery). The AWS IoT Core broker handles 500,000 concurrent connections with Rule Actions forwarding to Kinesis. Command delivery (unlock, lock, alarm) uses the MQTT retained message or IoT Core Job feature to ensure delivery even if the vehicle is momentarily disconnected. Commands are acknowledged with a response topic, enabling the app to confirm successful unlock within 3 seconds.

Geofence Enforcement Engine

Geofences fall into three types: operational zones (valid ride area), no-parking zones (cannot end ride here), and slow zones (vehicle firmware enforces speed cap via motor control). The Geofence Engine runs as a Kinesis consumer, evaluating each location update against a pre-loaded in-memory R-tree of active geofences. Zone entry/exit events are published to a Kafka topic. No-parking zone violations: the Ride Service sends an in-app alert and prevents trip end until the user moves out of the zone. Slow zone entry: the platform sends a speed_limit command to the vehicle firmware.

Fleet Rebalancing Service

The Fleet Intelligence Service runs hourly analysis: comparing current vehicle distribution (from DynamoDB aggregation by geohash cell) against predicted demand (ML model trained on historical ride origins by hour-of-day and day-of-week). It identifies imbalanced zones (supply/demand ratio < 0.5 or > 3) and generates rebalancing tasks. Tasks are assigned to field ops workers via the Ops App: "Move 5 scooters from Zone A to Zone B" with navigation to each vehicle's GPS location. Completed tasks update vehicle location in DynamoDB and close the task.

Database Design

Vehicle state in DynamoDB: partition key = vehicle_id, attributes = {status, lat, lng, battery_pct, lock_state, last_seen, geofences_member_of}. DynamoDB provides single-digit millisecond reads critical for the map query path. Active rides in DynamoDB: (ride_id, user_id, vehicle_id, start_time, start_location, status). Completed rides archived to S3 Parquet daily. Geofences in PostgreSQL + PostGIS (polygon geometry, type, pricing_modifier, speed_limit) with a GiST spatial index. Loaded into in-memory R-tree in Geofence Engine on startup, refreshed on geofence updates via Redis pub/sub.

API Design

  • GET /v1/vehicles/nearby?lat={}&lng={}&radius={} — Returns map of nearby available vehicles with location, battery level, and estimated range
  • POST /v1/rides — User reserves vehicle: accepts vehicle_id; reserves for 15 minutes, returns ride_id; sends unlock code to vehicle
  • POST /v1/rides/{ride_id}/lock — User ends ride: triggers vehicle lock command, computes fare from trip distance and duration, charges payment method
  • GET /v1/operations/tasks — Ops worker fetches assigned rebalancing/maintenance tasks with vehicle GPS locations

Scaling & Bottlenecks

The map query (nearby vehicles) is the highest-frequency user-facing query: 500 searches/second each querying DynamoDB for vehicles within a radius. DynamoDB's lack of native geospatial support means vehicle locations are pre-indexed in Redis GEO (GEOADD on every location update). GEORADIUS queries on Redis handle 50,000 queries/second. Vehicle location updates to Redis: 8,000/second, well within Redis capacity.

The unlock command path is latency-critical. The sequence is: app → API → IoT Core → vehicle firmware → firmware ACK → IoT Core → API → app. The median round-trip is ~1.5 seconds; the tail (99th percentile) can reach 5–8 seconds in poor cellular coverage. The app shows an animated unlock indicator during this window to manage user anxiety. A fallback QR code (physical on vehicle) allows offline unlock for vehicles with temporarily offline modems.

Key Trade-offs

  • Dockless vs. docked — dockless is more convenient (park anywhere within zone) but creates clutter and rebalancing headaches; docked systems are self-rebalancing but require expensive physical infrastructure
  • In-app unlock vs. physical key — in-app (via cellular command) is the primary path but depends on network connectivity; physical keypad or NFC provides offline fallback at added hardware cost
  • Battery swap vs. in-situ charging — battery swap (field ops replace depleted batteries) is faster but operationally complex; in-situ charging requires dock infrastructure; most scooter operators use battery swap vans
  • Speed limiting via software vs. hardware — software-enforced speed caps (motor controller commands) can be overridden by hardware tampering; dual-layer enforcement (firmware + physical governor) adds safety but complexity

GO DEEPER

Master this topic in our 12-week cohort

Our Advanced System Design cohort covers this and 11 other deep-dive topics with live sessions, assignments, and expert feedback.