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.
Asked at: DoorDash Interview Guide
Asked at: Uber Interview Guide
Asked at: Lyft Interview Guide
Asked at: Snap Interview Guide