Low-Level Design: Food Delivery Order System (DoorDash/Uber Eats) — State Machine, Driver Assignment

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

Scroll to Top