Requirements
Functional: customers browse restaurants and place orders, restaurants receive and confirm orders, the platform assigns delivery drivers, drivers pick up and deliver orders, real-time status tracking (order → confirmed → preparing → ready → picked_up → delivered), cancellation handling.
Non-functional: order placement is idempotent (no duplicate orders), status transitions are atomic, driver assignment is fair (nearest available driver), system handles peak-time burst (dinner rush).
Core Entities
from enum import Enum
from dataclasses import dataclass, field
from typing import Optional, List
from datetime import datetime
class OrderStatus(Enum):
PENDING = "PENDING" # placed, awaiting restaurant confirmation
CONFIRMED = "CONFIRMED" # restaurant accepted
PREPARING = "PREPARING" # kitchen started
READY = "READY" # ready for pickup
DRIVER_ASSIGNED = "DRIVER_ASSIGNED"
PICKED_UP = "PICKED_UP" # driver has the food
DELIVERED = "DELIVERED"
CANCELLED = "CANCELLED"
@dataclass
class OrderItem:
item_id: str
name: str
quantity: int
unit_price_cents: int
@dataclass
class Order:
order_id: str
customer_id: str
restaurant_id: str
driver_id: Optional[str]
items: List[OrderItem]
status: OrderStatus
subtotal_cents: int
delivery_fee_cents: int
tip_cents: int
total_cents: int
delivery_address: str
special_instructions: str
placed_at: datetime
estimated_delivery: Optional[datetime]
delivered_at: Optional[datetime]
cancellation_reason: Optional[str] = None
@dataclass
class Driver:
driver_id: str
name: str
is_available: bool
latitude: float
longitude: float
current_order_id: Optional[str]
rating: float
total_deliveries: int
@dataclass
class Restaurant:
restaurant_id: str
name: str
is_open: bool
average_prep_minutes: int
latitude: float
longitude: float
Order State Machine
VALID_TRANSITIONS = {
OrderStatus.PENDING: [OrderStatus.CONFIRMED, OrderStatus.CANCELLED],
OrderStatus.CONFIRMED: [OrderStatus.PREPARING, OrderStatus.CANCELLED],
OrderStatus.PREPARING: [OrderStatus.READY, OrderStatus.CANCELLED],
OrderStatus.READY: [OrderStatus.DRIVER_ASSIGNED],
OrderStatus.DRIVER_ASSIGNED: [OrderStatus.PICKED_UP, OrderStatus.READY], # driver unassigned -> back to READY
OrderStatus.PICKED_UP: [OrderStatus.DELIVERED],
OrderStatus.DELIVERED: [],
OrderStatus.CANCELLED: [],
}
def transition(order: Order, new_status: OrderStatus, reason: str = '') -> None:
if new_status not in VALID_TRANSITIONS[order.status]:
raise ValueError(f"Cannot transition {order.status} -> {new_status}")
order.status = new_status
if reason:
order.cancellation_reason = reason
db.save(order)
emit_event('order_status_changed', order)
Order Service
class OrderService:
def place_order(self, customer_id: str, restaurant_id: str,
items: List[dict], delivery_address: str,
idempotency_key: str) -> Order:
# Idempotency: return existing order if key already used
existing = db.get_order_by_idempotency_key(idempotency_key)
if existing:
return existing
restaurant = db.get_restaurant(restaurant_id)
if not restaurant.is_open:
raise ValueError("Restaurant is closed")
order_items = self._validate_items(restaurant_id, items)
subtotal = sum(i.unit_price_cents * i.quantity for i in order_items)
delivery_fee = self._calculate_delivery_fee(restaurant, delivery_address)
order = Order(
order_id=generate_id(),
customer_id=customer_id,
restaurant_id=restaurant_id,
driver_id=None,
items=order_items,
status=OrderStatus.PENDING,
subtotal_cents=subtotal,
delivery_fee_cents=delivery_fee,
tip_cents=0,
total_cents=subtotal + delivery_fee,
delivery_address=delivery_address,
special_instructions='',
placed_at=datetime.utcnow(),
estimated_delivery=self._estimate_delivery(restaurant),
)
db.save_order(order, idempotency_key=idempotency_key)
notify_restaurant(order)
return order
def cancel_order(self, order_id: str, reason: str, requester: str) -> Order:
order = db.get_order(order_id)
# Business rule: cannot cancel after driver picked up
if order.status in [OrderStatus.PICKED_UP, OrderStatus.DELIVERED]:
raise ValueError("Cannot cancel after pickup")
transition(order, OrderStatus.CANCELLED, reason)
if order.driver_id:
self._unassign_driver(order.driver_id)
self._refund(order, requester)
return order
Driver Assignment
import math
def haversine_distance(lat1, lon1, lat2, lon2) -> float:
"""Approximate distance in km between two lat/lon points."""
R = 6371
dlat = math.radians(lat2 - lat1)
dlon = math.radians(lon2 - lon1)
a = math.sin(dlat/2)**2 + math.cos(math.radians(lat1)) * math.cos(math.radians(lat2)) * math.sin(dlon/2)**2
return R * 2 * math.asin(math.sqrt(a))
class DriverAssignmentService:
def assign_driver(self, order: Order) -> Optional[Driver]:
restaurant = db.get_restaurant(order.restaurant_id)
available_drivers = db.get_available_drivers() # status=available, no current order
if not available_drivers:
return None
# Find nearest driver to restaurant
nearest = min(
available_drivers,
key=lambda d: haversine_distance(
d.latitude, d.longitude,
restaurant.latitude, restaurant.longitude
)
)
# Assign atomically
success = db.assign_driver_if_available(nearest.driver_id, order.order_id)
if not success:
return self.assign_driver(order) # retry if driver was just taken
nearest.current_order_id = order.order_id
nearest.is_available = False
order.driver_id = nearest.driver_id
transition(order, OrderStatus.DRIVER_ASSIGNED)
notify_driver(nearest, order)
notify_customer(order)
return nearest
Real-Time Status Updates
Use WebSocket connections (Socket.IO or server-sent events) for real-time order tracking. On each status transition, emit an event to a Kafka topic (order-events). A notification service consumes the topic and:
- Pushes status update to the customer’s WebSocket connection via a presence server (customer_id → connection mapping in Redis).
- Sends push notification (FCM/APNs) if the customer’s app is backgrounded.
- Updates the driver app’s current task.
ETA Calculation
def estimate_delivery(restaurant, driver, delivery_address) -> datetime:
driver_to_restaurant_km = haversine_distance(
driver.latitude, driver.longitude,
restaurant.latitude, restaurant.longitude
)
restaurant_to_customer_km = haversine_distance(
restaurant.latitude, restaurant.longitude,
*geocode(delivery_address)
)
avg_speed_kmh = 25 # urban driving speed
pickup_minutes = (driver_to_restaurant_km / avg_speed_kmh) * 60
delivery_minutes = (restaurant_to_customer_km / avg_speed_kmh) * 60
total = restaurant.average_prep_minutes + pickup_minutes + delivery_minutes
return datetime.utcnow() + timedelta(minutes=total)
Interview Questions
Q: How do you handle the case where a driver is assigned but doesn’t pick up the order?
Set a pickup deadline: order must be picked up within N minutes of assignment (e.g., 15 minutes). A background job checks orders in DRIVER_ASSIGNED status past their deadline. If expired: unassign the driver (mark driver available), transition order back to READY, and re-run driver assignment. Track failed assignments per driver — multiple failures trigger account review. Notify the customer of the delay.
Q: How do you prevent duplicate orders when a customer double-taps Place Order?
The client generates a unique idempotency key (UUID) per order attempt and sends it in the request header. The server stores the key in a unique-constrained column alongside the order. If a duplicate key arrives within the TTL window (e.g., 24 hours), return the existing order without creating a new one. The database unique constraint prevents race conditions if two requests arrive simultaneously with the same key — only one INSERT succeeds.
{
“@context”: “https://schema.org”,
“@type”: “FAQPage”,
“mainEntity”: [
{
“@type”: “Question”,
“name”: “How do you design the order state machine for a food delivery system?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “States: PENDING (placed) u2192 CONFIRMED (restaurant accepted) u2192 PREPARING (kitchen started) u2192 READY (food done) u2192 DRIVER_ASSIGNED u2192 PICKED_UP u2192 DELIVERED. Parallel path: any state except PICKED_UP and DELIVERED can transition to CANCELLED. A VALID_TRANSITIONS dict enforces legal moves. On each transition, emit an event to a message queue (Kafka) u2014 consumers update the customer’s real-time tracking, send push notifications, and update analytics. The state machine prevents invalid operations like delivering before pickup.”
}
},
{
“@type”: “Question”,
“name”: “How do you assign drivers to orders efficiently?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Store driver locations in Redis (updated every 4 seconds via the driver app). On order READY: query available drivers (status=available, no current order) from Redis. Compute haversine distance from each driver to the restaurant. Assign the nearest driver using an atomic Redis command (SET driver:{id}:order_id {order_id} NX) to prevent race conditions u2014 two orders can’t grab the same driver. If the nearest driver is taken, retry with the next nearest. For high-volume markets, use a spatial index (Redis GEO commands: GEOSEARCH) to query only drivers within a radius.”
}
},
{
“@type”: “Question”,
“name”: “How do you handle driver no-shows after assignment?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Set a pickup deadline: order must be picked up within N minutes of assignment (typically 10-15 minutes). A background job polls orders in DRIVER_ASSIGNED status past their deadline. On expiry: (1) Unassign the driver u2014 mark driver available, clear current_order_id. (2) Transition order back to READY. (3) Re-run driver assignment. (4) Notify the customer of the delay. Track failed pickups per driver u2014 multiple failures in a week trigger account review. Repeated driver-side cancellations after assignment affect driver rating.”
}
},
{
“@type”: “Question”,
“name”: “How do you prevent duplicate orders from double-taps or retries?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “The client generates a UUID idempotency_key per order attempt and sends it as a request header. The server stores the key in a unique-constrained column in the orders table. On duplicate key: the SELECT before INSERT finds the existing order and returns it without creating a new one. For true concurrent protection, use a unique database constraint on idempotency_key u2014 if two requests race in simultaneously, only one INSERT succeeds; the other gets a unique constraint violation and retries the SELECT to return the existing order. Keys expire after 24 hours.”
}
},
{
“@type”: “Question”,
“name”: “How would you scale a food delivery system to millions of orders per day?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Shard orders by restaurant_id or city_id (most queries are geographically scoped). Use Redis for driver locations and order status hot data (fast read/write). Use Kafka for the event bus: order state changes u2192 notifications, analytics, driver app updates. The driver assignment service reads from Redis GEO for proximity queries rather than querying the database. Use a CDN for restaurant menus (change infrequently, high read volume). During dinner rush, the bottleneck is driver assignment u2014 precompute proximity scores when drivers go online, cache for fast lookup during peak demand.”
}
}
]
}
Asked at: DoorDash Interview Guide
Asked at: Uber Interview Guide
Asked at: Lyft Interview Guide
Asked at: Snap Interview Guide