Core Entities
Customer: customer_id, name, email, phone, default_address, stripe_customer_id. Restaurant: restaurant_id, name, address, location (lat/lng), cuisine_type, rating, is_open, prep_time_minutes, operating_hours. MenuItem: item_id, restaurant_id, name, description, price, category, is_available, photo_url. Order: order_id, customer_id, restaurant_id, driver_id, status (PLACED, ACCEPTED, PREPARING, READY, PICKED_UP, DELIVERED, CANCELLED), items (JSON), subtotal, delivery_fee, tip, total, delivery_address, special_instructions, placed_at, accepted_at, ready_at, picked_up_at, delivered_at. Driver: driver_id, name, phone, vehicle_type, status (OFFLINE, ONLINE, ON_DELIVERY), current_location (lat/lng), last_location_update. DriverLocation: driver_id, lat, lng, heading, speed, timestamp (time-series, not persisted long-term). Review: review_id, order_id, customer_id, restaurant_rating (1-5), driver_rating (1-5), comment, created_at.
Order State Machine
class OrderService:
VALID_TRANSITIONS = {
"PLACED": ["ACCEPTED", "CANCELLED"],
"ACCEPTED": ["PREPARING", "CANCELLED"],
"PREPARING": ["READY"],
"READY": ["PICKED_UP"],
"PICKED_UP": ["DELIVERED"],
"DELIVERED": [],
"CANCELLED": [],
}
def transition_order(self, order_id: int, new_status: str,
actor_id: int, actor_type: str) -> Order:
with db.transaction():
order = db.query(
"SELECT * FROM orders WHERE order_id = %s FOR UPDATE", order_id
)
if new_status not in self.VALID_TRANSITIONS[order.status]:
raise InvalidTransition(
f"{order.status} -> {new_status} not allowed"
)
timestamp_col = {
"ACCEPTED": "accepted_at",
"READY": "ready_at",
"PICKED_UP": "picked_up_at",
"DELIVERED": "delivered_at",
"CANCELLED": "cancelled_at",
}.get(new_status)
update = {"status": new_status}
if timestamp_col:
update[timestamp_col] = "NOW()"
db.update("orders", update, where={"order_id": order_id})
self.notify_parties(order, new_status)
return db.get_order(order_id)
Driver Dispatch
When an order is READY (or slightly before, based on prep time estimate): find the best available driver. Geospatial query: drivers store current_location in a PostGIS geography column or Redis geospatial index (GEOADD/GEORADIUS). Query available drivers within radius R (e.g., 5km) sorted by distance. Driver scoring: score = w1 * distance + w2 * estimated_delivery_time + w3 * driver_rating. Distance: straight-line (haversine) approximation; use routing API (Google Maps Distance Matrix) for accurate ETA at assignment time. Offer flow: send the assignment offer to the top-scored driver via push notification. Driver has 30 seconds to accept. If declined or timeout: remove driver from candidates and try the next. Repeat until accepted or no drivers available (escalate alert). Batch dispatch: for efficiency, group nearby orders and offer them together to one driver (multi-restaurant pickup), reducing per-order delivery cost.
Real-Time Location Tracking
Drivers send location updates every 3-5 seconds via a WebSocket connection or mobile SDK. Updates flow: Driver app → WebSocket gateway → Kafka topic (driver_locations). A location consumer processes updates: updates Redis GEOADD (for dispatch queries) and publishes to a customer-facing WebSocket channel for live map updates. Customers receive driver location via WebSocket: connect to wss://api.example.com/orders/{id}/track. On each driver location event: push lat/lng/ETA to all subscribers for that order. ETA recalculation: on each location update, call the routing API (or a lightweight ETA model) and push updated ETA to the customer. Persist only final delivery location (not every update) to the database — location history is too high-volume for a relational DB. Use a time-series store (InfluxDB, TimescaleDB) or discard after 24 hours.
Estimated Delivery Time (EDT) Prediction
EDT = prep_time + driver_travel_time + buffer. Prep time: restaurant-specific estimate based on order complexity and historical data. Use average prep time + percentile buffer (e.g., p75 of historical prep times for this restaurant at this hour). Driver travel time: restaurant → customer distance / average speed, or routing API call. Factors: time of day (rush hour), weather (reduce speed estimate by 30% in rain). ML approach: train a gradient boosted model on historical delivery data with features: restaurant, order size, time of day, driver vehicle type, distance, weather, day of week. Predicted p50 and p90 delivery time. Show customers a range: “35-50 minutes” rather than a point estimate — more honest and reduces support contacts from customers who see the exact estimate slip by 2 minutes.
See also: DoorDash Interview Prep
See also: Uber Interview Prep
See also: Lyft Interview Prep
See also: Shopify Interview Guide
See also: Airbnb Interview Guide 2026: Search Systems, Trust and Safety, and Full-Stack Engineering