system-design48 min read

How to Design a Food Delivery Platform — A Senior+ Guide | Ayodhyya

How to Design a Food Delivery Platform

A Comprehensive Senior+ Engineering Guide — From Order Placement to Last-Mile Delivery

26 Sections 10,000+ Words System Design Architecture Deep Dive

1. Introduction & Why Food Delivery is Hard

Food delivery platforms like Uber Eats, DoorDash, and Grubhub have fundamentally changed how people eat. The global online food delivery market is projected to exceed $500 billion by 2030. Yet behind the simple act of tapping "Order" lies one of the most complex real-time systems in consumer technology — a platform that must orchestrate restaurants, customers, and delivery drivers across thousands of cities in real time.

Food delivery is uniquely challenging compared to other on-demand platforms for several reasons. First, it involves perishable goods with strict time constraints — a pizza that arrives in 60 minutes is a fundamentally different product than one that arrives in 25. Second, it requires real-time coordination of three independent parties (customer, restaurant, driver) who each have their own state and timeline. Third, the system must handle extreme demand spikes — Friday dinner rush in New York City can see 10x normal traffic within a 30-minute window.

Why This Matters for System Design: Food delivery is a favorite interview topic because it tests your ability to design systems with real-time constraints, multi-party coordination, geospatial queries, and financial transactions — all in a single platform.

The Three-Sided Marketplace Problem

Unlike ride-sharing (which has two sides: rider and driver), food delivery is a three-sided marketplace. Each side has different needs and constraints:

  • Customers want fast delivery, accurate ETAs, live tracking, and fair prices. They will abandon the app if the estimated delivery time exceeds 45 minutes.
  • Restaurants want efficient order management, kitchen integration, accurate inventory, and timely payments. They operate on thin margins and can't afford to turn away orders they can fulfill or accept orders they can't.
  • Drivers want fair compensation, efficient routes, order batching, and transparent earnings. High driver churn is one of the biggest operational challenges in the industry.

Scale at a Glance

MetricScale (Major Platform)
Daily active users30M+
Orders per peak hour500K+
Restaurants on platform800K+
Active delivery drivers2M+
Cities served6,000+
Average delivery time25-35 minutes
GPS updates per driver per minute4-6
Peak QPS (orders per second)15,000+

2. Requirements (Functional & Non-Functional)

Functional Requirements

  1. Browse & Search: Customers discover restaurants by location, cuisine, rating, price range, delivery time, and promotions.
  2. Menu Management: Restaurants create and update menus with items, modifiers, pricing, availability, and preparation times.
  3. Cart & Checkout: Customers add items, apply promo codes, see itemized pricing (subtotal, tax, delivery fee, service fee, tip), and pay.
  4. Order Tracking: Customers see real-time order status (placed → confirmed → preparing → ready → picked up → delivered) and driver location on a map.
  5. Driver Matching & Routing: The system assigns available drivers to ready orders and optimizes multi-stop routes.
  6. Payment Processing: Handle payments from customers and split payouts to restaurants and drivers minus platform commission.
  7. Notifications: Push notifications and SMS for order status changes, driver arrival, and promotions.
  8. Ratings & Reviews: Two-sided rating: customer rates restaurant and driver; restaurant can rate customer behavior.
  9. Customer Support: In-app support for missing items, wrong orders, refunds, and complaints.
  10. Analytics: Restaurant portal with sales, menu performance, and customer insights. Driver dashboard with earnings and efficiency metrics.

Non-Functional Requirements

RequirementTargetWhy
Availability99.99% (52 min downtime/year)Every minute of downtime during dinner rush loses millions
Order latency (P99)< 500msCheckout must feel instant
Search latency (P95)< 200msUsers expect instant search results
Location update throughput50K GPS events/sec2M drivers × 5 updates/min ÷ 60
ETA accuracy±3 minutes (P80)Under-promising and over-delivering builds trust
Data durability99.999999999% (11 nines)Payment and order data cannot be lost
Consistency modelStrong for payments, eventual for trackingFinancial correctness vs. real-time freshness
Geo query latency< 50msNearby restaurant search must be fast

3. High-Level Architecture

The food delivery platform consists of several microservices communicating through event streams (Kafka) and synchronous APIs (gRPC/REST). The architecture separates concerns cleanly: customer-facing services handle browsing and ordering, operations services handle logistics, and infrastructure services handle payments, notifications, and analytics.

graph TB
    subgraph "Client Layer"
        CustomerApp["Customer App
(iOS/Android/Web)"] RestaurantApp["Restaurant App
(Tablet/KDS)"] DriverApp["Driver App
(iOS/Android)"] AdminPortal["Admin Portal
(Internal)"] end subgraph "API Gateway" Gateway["API Gateway
(Rate Limiting, Auth, Routing)"] end subgraph "Core Services" SearchSvc["Search Service
(Elasticsearch)"] OrderSvc["Order Service
(PostgreSQL)"] CartSvc["Cart Service
(Redis)"] MenuSvc["Menu Service
(PostgreSQL)"] RestaurantSvc["Restaurant Service
(PostgreSQL)"] PricingSvc["Pricing Service"] DriverMatchSvc["Driver Matching
Service"] RouteSvc["Route & ETA
Service"] PaymentSvc["Payment Service
(Stripe/Adyen)"] NotificationSvc["Notification Service"] SupportSvc["Support Service"] RatingSvc["Rating Service"] PromotionSvc["Promotion Service"] AnalyticsSvc["Analytics Service"] end subgraph "Data Layer" PostgreSQL[("PostgreSQL
(Orders, Users, Restaurants)")] Redis[("Redis Cluster
(Cart, Sessions, Realtime)")] Elasticsearch[("Elasticsearch
(Search Index)")] Kafka["Apache Kafka
(Event Stream)"] S3["S3/Object Storage
(Images, Receipts)"] ClickHouse[("ClickHouse
(Analytics)")] end subgraph "Real-time Layer" WebSocket["WebSocket Gateway
(Location, Status)"] PubSub["Redis Pub/Sub
(Real-time Events)"] end CustomerApp --> Gateway RestaurantApp --> Gateway DriverApp --> Gateway AdminPortal --> Gateway Gateway --> SearchSvc Gateway --> OrderSvc Gateway --> CartSvc Gateway --> MenuSvc Gateway --> RestaurantSvc Gateway --> PaymentSvc Gateway --> DriverMatchSvc Gateway --> RatingSvc Gateway --> PromotionSvc OrderSvc --> Kafka Kafka --> DriverMatchSvc Kafka --> NotificationSvc Kafka --> AnalyticsSvc Kafka --> RouteSvc DriverMatchSvc --> WebSocket WebSocket --> DriverApp WebSocket --> CustomerApp OrderSvc --> PostgreSQL CartSvc --> Redis SearchSvc --> Elasticsearch RestaurantSvc --> PostgreSQL AnalyticsSvc --> ClickHouse

Key Architectural Decisions

  • Event-Driven Core: Every state transition (order placed, driver assigned, order picked up) emits a Kafka event. This decouples services and enables real-time analytics, notifications, and auditing.
  • CQRS for Search: Menu and restaurant data is written to PostgreSQL, then asynchronously indexed into Elasticsearch via Kafka Connect. This separates read-heavy search workloads from write-heavy transactional workloads.
  • WebSocket for Real-time: Driver GPS updates and order status changes flow through a WebSocket gateway backed by Redis Pub/Sub. This avoids polling and keeps connections efficient.
  • Multi-Region: Each metro area runs in its own Kubernetes cluster with local PostgreSQL primary and Redis. Cross-region replication exists only for user accounts and payment data. Orders are always processed in the region closest to the restaurant.

4. Restaurant Management & Menu Ingestion

The restaurant and menu system is the foundation of the platform. Restaurants onboard, create menus, manage availability, and receive orders. Menu data flows through a multi-stage pipeline to ensure accuracy and freshness.

Menu Ingestion Pipeline

graph LR
    A["Restaurant
Creates Menu"] --> B["Menu Validation
Service"] B --> C["Image Processing
(S3 + CDN)"] B --> D["Price & Tax
Enrichment"] D --> E["Elasticsearch
Index Update"] B --> F["Kafka Event
(menu.updated)"] F --> G["Menu Cache
Invalidation"] F --> H["Search Index
Rebuild"]

Menu Data Model

C#
public class Restaurant
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public Address Address { get; set; }
    public GeoPoint Location { get; set; }
    public string CuisineType { get; set; }
    public decimal Rating { get; set; }
    public int TotalRatings { get; set; }
    public Money PriceRange { get; set; } // 1-4 dollar signs
    public TimeSpan AveragePrepTime { get; set; }
    public BusinessHours OperatingHours { get; set; }
    public List<MenuCategory> Menu { get; set; }
    public DeliveryConfig DeliveryConfig { get; set; }
    public RestaurantStatus Status { get; set; }
}

public class MenuItem
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public Money Price { get; set; }
    public List<ItemModifierGroup> ModifierGroups { get; set; }
    public List<string> DietaryTags { get; set; }
    public bool IsAvailable { get; set; }
    public TimeSpan PrepTime { get; set; }
    public int PopularityScore { get; set; }
    public int Calories { get; set; }
    public List<string> ImageUrls { get; set; }
}

public class ItemModifierGroup
{
    public string Name { get; set; }
    public bool IsRequired { get; set; }
    public int MinSelections { get; set; }
    public int MaxSelections { get; set; }
    public List<ItemModifier> Modifiers { get; set; }
}

public class ItemModifier
{
    public string Name { get; set; }
    public Money Price { get; set; }
    public bool IsDefault { get; set; }
}

Dynamic Availability

Restaurant availability is not binary — it changes based on time of day, order volume, and ingredient stock. The system tracks three levels of availability:

  • Temporal availability: Based on operating hours. The restaurant can set different hours for delivery, pickup, and dine-in.
  • Capacity-based availability: When the kitchen is at maximum capacity, the platform temporarily pauses new orders. This is determined by monitoring active order count against the restaurant's configured throughput rate.
  • Item-level availability: Individual items can be marked as sold out. Many restaurants do this through the KDS in real time as ingredients run out.
Optimization Tip: Cache menu data aggressively with a 30-second TTL. Menu changes are infrequent, but reads happen thousands of times per second. Use cache-aside pattern with Redis and invalidate on menu.updated events from Kafka.

5. Search & Discovery

Search is the primary discovery mechanism. Users search by cuisine, restaurant name, dish name, or dietary preference. The system must return relevant results filtered by delivery location in under 200ms.

Search Architecture

graph LR
    A["User Query
(text + location)"] --> B["API Gateway"] B --> C["Search Service"] C --> D["Elasticsearch
(full-text + geo)"] C --> E["Restaurant
Availability
Service"] C --> F["Personalization
Service"] D --> G["Ranked Results"] E --> G F --> G G --> H["Response
(<200ms)"]

Geo-Filtered Search

C#
public class SearchService
{
    private readonly IElasticClient _elastic;
    private readonly IAvailabilityChecker _availability;

    public async Task<SearchResult> SearchRestaurantsAsync(
        string query, GeoPoint userLocation, int radiusKm,
        SearchFilters filters)
    {
        var searchResponse = await _elastic.SearchAsync<RestaurantIndex>(s => s
            .Query(q => q
                .Bool(b => b
                    .Must(
                        m => m.MultiMatch(mm => mm
                            .Fields(f => f
                                .Field(p => p.Name, 2.0)
                                .Field(p => p.CuisineType, 1.5)
                                .Field(p => p.MenuItems, 1.0))
                            .Query(query)
                            .Type(TextQueryType.BestFields)),
                        m => m.GeoDistance(g => g
                            .Field(p => p.Location)
                            .Distance($"{radiusKm}km")
                            .DistanceType(GeoDistanceType.Arc)
                            .Location(userLocation))))
                    .Filter(
                        f => f.Term(t => t.Status, "open"),
                        f => f.Range(r => r
                            .Number(n => n
                                .Field(p => p.Rating)
                                .Gte(filters.MinRating ?? 0))))))
            .Sort(so => so
                .GeoDistance(g => g
                    .Field(p => p.Location)
                    .Location(userLocation)
                    .Order(SortOrder.Ascending)
                    .Unit(DistanceUnit.Kilometers)))
            .Size(50));

        var restaurants = searchResponse.Documents.ToList();
        var availability = await _availability
            .CheckBulkAsync(restaurants.Select(r => r.Id));

        return new SearchResult
        {
            Restaurants = restaurants
                .Where(r => availability[r.Id].IsAvailable)
                .Select(r => MapToResult(r, userLocation))
                .ToList()
        };
    }
}

Search Ranking Factors

FactorWeightDescription
Relevance score35%Full-text match quality (name, cuisine, dish names)
Proximity25%Distance from user's delivery address
Rating15%Average rating and number of reviews
Estimated delivery time15%Lower is better — factoring in prep + delivery
Popularity10%Order volume in the last 7 days
Personalization Layer: After the initial geo-filtered search, a personalization service re-ranks results based on the user's order history. Users who frequently order Chinese food see Chinese restaurants boosted. Users who ordered from a restaurant before see it promoted. This personalization happens in-memory via a pre-computed user preference vector stored in Redis.

Search Index Design

The Elasticsearch index for restaurants is carefully designed to support fast geo-filtered, full-text search with complex filtering. Each restaurant document contains denormalized menu item data so that a single query can match against restaurant names, cuisine types, and individual dish names simultaneously.

JSON
{
    "mappings": {
        "properties": {
            "name": { "type": "text", "analyzer": "standard" },
            "cuisine_type": { "type": "keyword" },
            "location": { "type": "geo_point" },
            "rating": { "type": "float" },
            "total_ratings": { "type": "integer" },
            "price_range": { "type": "integer" },
            "avg_prep_time_min": { "type": "integer" },
            "status": { "type": "keyword" },
            "operating_hours": { "type": "nested" },
            "menu_items": {
                "type": "nested",
                "properties": {
                    "name": { "type": "text" },
                    "price": { "type": "float" },
                    "dietary_tags": { "type": "keyword" },
                    "category": { "type": "keyword" }
                }
            },
            "popularity_7d": { "type": "integer" },
            "delivery_fee_range": { "type": "integer_range" }
        }
    }
}

Search Failover and Degradation

Search is the front door of the platform — if search is down, no orders can be placed. The system implements a multi-tier degradation strategy: (1) Primary: Elasticsearch cluster with 3 master-eligible nodes and 5 data nodes. (2) Failover: If Elasticsearch is unreachable, fall back to a cached search result set stored in Redis, refreshed every 60 seconds. These cached results cover the most common location + cuisine combinations. (3) Last resort: Serve a static "popular restaurants" list per city from a CDN-backed JSON file, updated hourly. This tier ensures that even during a complete search outage, customers can still browse and order from known restaurants.

6. Real-Time Order Placement Flow

Order placement is the most critical transactional flow. It must be fast, reliable, and consistent. A single order triggers events across multiple services simultaneously.

Order Placement Sequence

sequenceDiagram
    participant C as Customer
    participant GW as API Gateway
    participant OS as Order Service
    participant PS as Pricing Service
    participant MS as Menu Service
    participant PG as PostgreSQL
    participant K as Kafka
    participant NS as Notification Service
    participant KDS as Kitchen Display

    C->>GW: POST /api/orders
    GW->>OS: Create Order Request
    OS->>MS: Validate Menu Items
    MS-->>OS: Items Valid + Prices
    OS->>PS: Calculate Total
    PS-->>OS: Final Price (tax, fees, tip)
    OS->>PG: BEGIN TRANSACTION
    PG-->>OS: Order Saved (status: PLACED)
    OS->>K: Emit OrderPlaced Event
    K-->>NS: Notify Customer (Order Confirmed)
    K-->>KDS: Send to Restaurant
    OS-->>GW: Order Confirmation
    GW-->>C: Order Placed (202 Accepted)

Order Service Implementation

C#
public class OrderService : IOrderService
{
    private readonly IOrderRepository _orders;
    private readonly IMenuService _menuService;
    private readonly IPricingEngine _pricing;
    private readonly IEventPublisher _events;
    private readonly IPaymentGateway _payments;

    public async Task<OrderResult> PlaceOrderAsync(
        PlaceOrderRequest request)
    {
        // 1. Validate menu items and get current prices
        var menuItems = await _menuService
            .ValidateAndGetPricesAsync(request.Items);

        if (menuItems.Any(i => !i.IsAvailable))
            throw new MenuItemUnavailableException(
                menuItems.First(i => !i.IsAvailable).Name);

        // 2. Calculate total price
        var pricingResult = await _pricing.CalculateAsync(
            new PricingContext
            {
                Items = request.Items,
                RestaurantId = request.RestaurantId,
                DeliveryAddress = request.DeliveryAddress,
                PromoCode = request.PromoCode,
                Tip = request.Tip
            });

        // 3. Authorize payment (hold, don't capture)
        var paymentHold = await _payments.AuthorizeAsync(
            new PaymentAuthorization
            {
                Amount = pricingResult.TotalAmount,
                Currency = "USD",
                PaymentMethodId = request.PaymentMethodId,
                IdempotencyKey = request.IdempotencyKey
            });

        if (!paymentHold.IsSuccess)
            throw new PaymentFailedException(paymentHold.Error);

        // 4. Create order in single transaction
        var order = await _orders.CreateAsync(new Order
        {
            Id = Guid.NewGuid(),
            CustomerId = request.CustomerId,
            RestaurantId = request.RestaurantId,
            Items = request.Items,
            Pricing = pricingResult,
            DeliveryAddress = request.DeliveryAddress,
            PaymentAuthorizationId = paymentHold.AuthorizationId,
            Status = OrderStatus.Placed,
            PlacedAt = DateTime.UtcNow,
            EstimatedDelivery = pricingResult.EstimatedDelivery
        });

        // 5. Emit events
        await _events.PublishAsync(new OrderPlacedEvent
        {
            OrderId = order.Id,
            RestaurantId = order.RestaurantId,
            Items = order.Items,
            TotalAmount = order.Pricing.TotalAmount
        });

        return new OrderResult
        {
            OrderId = order.Id,
            Status = order.Status,
            EstimatedDelivery = order.EstimatedDelivery
        };
    }
}
Idempotency is Critical: The IdempotencyKey prevents duplicate orders when users double-tap "Place Order" or when network retries occur. The API gateway checks for existing orders with the same key within a 5-minute window before processing a new request.

7. Cart & Pricing Engine

The cart and pricing engine calculates the total cost of an order. This is deceptively complex because pricing involves multiple layers: base item prices, modifier additions, quantity discounts, tax calculation (which varies by jurisdiction), delivery fees, service fees, small order fees, surge pricing, and tips.

Pricing Breakdown

ComponentCalculationExample ($25 order)
SubtotalSum of item prices × quantities$22.00
Modifier chargesAdd-ons (extra cheese, avocado, etc.)$3.00
TaxLocal tax rate × taxable amount$1.98
Delivery feeDistance-based + driver supply factor$3.99
Service feePlatform commission (10-15%)$2.50
Small order feeIf subtotal < $10 threshold$0.00
Promo discountCoupon code applied-$5.00
TipUser-selected (default: 15%)$3.75
Total$32.22

Pricing Engine Implementation

C#
public class PricingEngine : IPricingEngine
{
    private readonly ITaxCalculator _tax;
    private readonly IDeliveryFeeCalculator _deliveryFee;
    private readonly IPromoEngine _promos;
    private readonly IDynamicPricingService _surge;
    private readonly IJurisdictionService _jurisdiction;

    public async Task<PricingResult> CalculateAsync(
        PricingContext ctx)
    {
        // 1. Calculate subtotal from items
        var subtotal = ctx.Items.Sum(i =>
            i.Price * i.Quantity
            + i.Modifiers.Sum(m => m.Price * i.Quantity));

        // 2. Calculate tax based on delivery address jurisdiction
        var jurisdiction = await _jurisdiction
            .ResolveAsync(ctx.DeliveryAddress);
        var taxAmount = await _tax.CalculateAsync(
            subtotal, jurisdiction);

        // 3. Calculate delivery fee (distance + demand based)
        var restaurant = await _restaurants
            .GetAsync(ctx.RestaurantId);
        var deliveryFee = await _deliveryFee.CalculateAsync(
            new DeliveryFeeContext
            {
                RestaurantLocation = restaurant.Location,
                DeliveryAddress = ctx.DeliveryAddress,
                CurrentDemand = await _surge
                    .GetDemandMultiplierAsync(ctx.DeliveryAddress),
                DriverAvailability = await _drivers
                    .GetAvailableCountAsync(ctx.DeliveryAddress)
            });

        // 4. Service fee (percentage of subtotal)
        var serviceFee = subtotal * 0.12m;

        // 5. Small order fee
        var smallOrderFee = subtotal < 10m ? 2.99m : 0m;

        // 6. Apply promo code
        var discount = await _promos.ApplyAsync(
            ctx.PromoCode, ctx.CustomerId, subtotal);

        // 7. Calculate total
        var preTipTotal = subtotal + taxAmount + deliveryFee
            + serviceFee + smallOrderFee - discount.Amount;

        return new PricingResult
        {
            Subtotal = subtotal,
            Tax = taxAmount,
            DeliveryFee = deliveryFee,
            ServiceFee = serviceFee,
            SmallOrderFee = smallOrderFee,
            Discount = discount.Amount,
            Tip = ctx.Tip,
            TotalAmount = preTipTotal + ctx.Tip,
            Breakdown = new PriceBreakdown
            {
                RestaurantPays = subtotal * 0.70m, // 70% to restaurant
                DriverPays = deliveryFee + ctx.Tip,
                PlatformKeeps = serviceFee + (subtotal * 0.30m)
            }
        };
    }
}
Pricing Accuracy Requirement: Pricing calculations must be deterministic and auditable. Every component (tax, fees, discounts) must be logged with its calculation inputs. This is critical for dispute resolution and regulatory compliance. Never round intermediate values — only round the final total to the nearest cent.

8. Order State Machine

Every order passes through a well-defined sequence of states. The state machine governs transitions, triggers notifications, and ensures the system stays consistent across all parties.

State Diagram

stateDiagram-v2
    [*] --> Placed : Customer places order
    Placed --> Confirmed : Restaurant accepts
    Placed --> Cancelled : Restaurant declines / timeout
    Confirmed --> Preparing : Kitchen starts cooking
    Preparing --> Ready : Food is ready
    Ready --> DriverAssigned : Driver picks up order
    DriverAssigned --> InTransit : Driver leaves restaurant
    InTransit --> Delivered : Driver marks delivered
    Delivered --> Completed : Customer confirms receipt
    Delivered --> DisputeOpened : Customer reports issue
    DisputeOpened --> Refunded : Support approves refund
    DisputeOpened --> Completed : Support rejects dispute
    Cancelled --> Refunded : Auto-refund processed
    Completed --> Reviewed : Customer leaves review

    note right of Placed : 15 min timeout for acceptance
    note right of Ready : 5 min notification to driver
    note right of InTransit : Live GPS tracking active
    note right of Delivered : 48 hr window for disputes

State Transition Service

C#
public class OrderStateMachine
{
    private static readonly Dictionary<OrderStatus, HashSet<OrderStatus>>
        _validTransitions = new()
    {
        [OrderStatus.Placed] = new()
            { OrderStatus.Confirmed, OrderStatus.Cancelled },
        [OrderStatus.Confirmed] = new()
            { OrderStatus.Preparing },
        [OrderStatus.Preparing] = new()
            { OrderStatus.Ready },
        [OrderStatus.Ready] = new()
            { OrderStatus.DriverAssigned },
        [OrderStatus.DriverAssigned] = new()
            { OrderStatus.InTransit },
        [OrderStatus.InTransit] = new()
            { OrderStatus.Delivered },
        [OrderStatus.Delivered] = new()
            { OrderStatus.Completed, OrderStatus.DisputeOpened },
        [OrderStatus.DisputeOpened] = new()
            { OrderStatus.Refunded, OrderStatus.Completed },
        [OrderStatus.Cancelled] = new()
            { OrderStatus.Refunded }
    };

    public async Task TransitionAsync(
        Order order, OrderStatus newStatus, string triggeredBy)
    {
        if (!_validTransitions.TryGetValue(order.Status,
            out var validTargets) || !validTargets.Contains(newStatus))
        {
            throw new InvalidTransitionException(
                order.Id, order.Status, newStatus);
        }

        var previousStatus = order.Status;
        order.Status = newStatus;
        order.StatusHistory.Add(new StatusTransition
        {
            From = previousStatus,
            To = newStatus,
            Timestamp = DateTime.UtcNow,
            TriggeredBy = triggeredBy
        });

        await _orders.UpdateAsync(order);

        // Publish state change event
        await _events.PublishAsync(new OrderStatusChangedEvent
        {
            OrderId = order.Id,
            PreviousStatus = previousStatus,
            NewStatus = newStatus,
            TriggeredBy = triggeredBy,
            Timestamp = DateTime.UtcNow
        });
    }
}

Timeout Handling

State transitions have time limits. If a restaurant doesn't confirm within 15 minutes, the order is automatically cancelled and the customer is refunded. If a driver isn't assigned within 10 minutes of the order being ready, the system expands the driver search radius and applies a bonus incentive. These timeouts are enforced by background workers that scan for stale orders.

9. Restaurant Kitchen Display System (KDS) Integration

The Kitchen Display System is the restaurant's interface for receiving and managing incoming orders. It replaces the traditional ticket printer and provides real-time order management, prep time estimation, and communication with drivers.

KDS Features

  • Order Queue: New orders appear with a countdown timer showing the estimated pickup time. Orders are color-coded: green (on time), yellow (approaching deadline), red (overdue).
  • Prep Time Adjustment: Restaurants can adjust prep time per order. If the kitchen is backed up, they can increase the estimated time, which automatically updates the driver dispatch timing.
  • Item-Level Status: Individual items can be marked as "started," "cooking," or "ready." This granular status is visible to the customer through the tracking interface.
  • Order Batching: Multiple orders for the same restaurant appear in a consolidated view, helping the kitchen prioritize and batch preparation.
  • Out-of-Stock Management: Restaurants can mark individual items as sold out in real time, which immediately updates the customer-facing menu and affects search indexing.

KDS Communication Protocol

C#
// Real-time order push to KDS via WebSocket
public class KDsConnectionHandler : WebSocketHandler
{
    private readonly IOrderService _orders;
    private readonly IEventBus _events;

    protected override async Task OnOrderPlacedAsync(Order order)
    {
        var kdsMessage = new KdsOrderMessage
        {
            Type = KdsMessageType.NewOrder,
            OrderId = order.Id,
            Items = order.Items.Select(i => new KdsItem
            {
                Name = i.Name,
                Quantity = i.Quantity,
                Modifiers = i.Modifiers,
                SpecialInstructions = i.Instructions
            }).ToList(),
            OrderTime = order.PlacedAt,
            EstimatedPickup = order.EstimatedPickup,
            CustomerName = order.Customer.FirstName + " " +
                order.Customer.LastName[0] + "."
        };

        await SendToRestaurantAsync(
            order.RestaurantId, kdsMessage);
    }

    protected override async Task OnPrepTimeUpdatedAsync(
        Guid orderId, TimeSpan newPrepTime)
    {
        // Update restaurant's prep time estimate
        var order = await _orders.GetAsync(orderId);
        var newPickup = DateTime.UtcNow + newPrepTime;

        // Notify driver service to adjust dispatch timing
        await _events.PublishAsync(new PrepTimeUpdatedEvent
        {
            OrderId = orderId,
            NewEstimatedPickup = newPickup
        });

        // Notify customer of updated ETA
        await _events.PublishAsync(new EtaUpdatedEvent
        {
            OrderId = orderId,
            NewEta = newPickup.Add(order.DeliveryEstimate)
        });
    }
}
Reliability: KDS connections are critical — a dropped connection means missed orders. The system maintains persistent WebSocket connections with automatic reconnection, falls back to polling every 5 seconds, and can send orders via SMS as a last resort for restaurants without reliable internet.

Order Lifecycle in the Kitchen

From the restaurant's perspective, an order moves through several kitchen stages that are visible to both the customer and the platform. The KDS tracks these stages to provide granular status updates. When an order arrives, it enters the "new" queue. The restaurant acknowledges it, moving it to "confirmed." A line cook starts working on it, marking it "in progress." When all items are cooked and plated, it transitions to "ready for pickup." If there are complications — a missing ingredient, equipment failure, or unexpected volume — the restaurant can mark the order as "delayed" with an updated prep time, which immediately notifies the customer and driver service. This granular visibility is a significant competitive advantage over phone-based order systems where customers have no insight into their order's progress.

10. Delivery Driver Assignment

Driver assignment is a real-time matching problem that must consider distance, driver capacity, batch efficiency, and driver preferences. The system matches available drivers to ready orders in under 30 seconds.

Matching Algorithm

graph TB
    A["Order Ready
Event"] --> B["Find Candidate
Drivers"] B --> C{"Within 3km
radius?"} C -->|Yes| D["Calculate Match
Score"] C -->|No| E["Expand Radius
to 5km"] E --> C D --> F{"Score > Threshold?"} F -->|Yes| G["Send Offer
to Top Driver"] F -->|No| H["Apply Bonus
Incentive"] H --> G G --> I{"Driver Accepts?"} I -->|Yes| J["Assign Driver
Update Order"] I -->|No (15s timeout)| K["Offer to Next
Candidate"] K --> G

Match Score Calculation

C#
public class DriverMatchingService
{
    private readonly IDriverLocationStore _locations;
    private readonly IBatchOptimizer _batchOptimizer;

    public async Task<List<DriverMatch>> FindMatchesAsync(
        Order order, TimeSpan maxWaitTime)
    {
        // 1. Find nearby available drivers
        var candidates = await _locations
            .FindNearbyAsync(
                order.Restaurant.Location,
                radiusKm: 3.0,
                maxDrivers: 20,
                status: DriverStatus.Available);

        // 2. Score each candidate
        var scored = candidates.Select(d => new DriverMatch
        {
            Driver = d,
            Score = CalculateMatchScore(d, order),
            BatchOpportunity = _batchOptimizer
                .FindBatchPotential(d, order)
        })
        .OrderByDescending(m => m.Score)
        .ToList();

        // 3. Check for batch optimization
        foreach (var match in scored)
        {
            var batch = match.BatchOpportunity;
            if (batch != null &&
                batch.AdditionalOrders > 0)
            {
                // Boost score for drivers who can batch
                match.Score *= 1.3m;
                match.BatchInfo = $"Batch with " +
                    $"{batch.AdditionalOrders} orders, " +
                    $"extra ${batch.BonusEarnings}";
            }
        }

        return scored.OrderByDescending(m => m.Score)
            .Take(5).ToList();
    }

    private decimal CalculateMatchScore(
        Driver driver, Order order)
    {
        var distance = GeoCalculator
            .DistanceKm(driver.Location,
                order.Restaurant.Location);
        var waitTime = (order.EstimatedPickup
            - DateTime.UtcNow).TotalMinutes;

        // Weighted scoring factors
        var distanceScore = Math.Max(0,
            100 - (distance * 20)); // 0-100, decay by 20/km
        var waitScore = waitTime > 0
            ? Math.Min(100, waitTime * 10) : 0;
        var ratingScore = driver.AverageRating * 20;
        var acceptanceScore =
            driver.AcceptanceRate * 50;

        // Check delivery zone compatibility
        var zoneMatch = driver.PreferredZones
            .Contains(order.DeliveryZone) ? 20 : 0;

        return (decimal)(
            distanceScore * 0.35 +
            waitScore * 0.15 +
            ratingScore * 0.20 +
            acceptanceScore * 0.10 +
            zoneMatch);
    }
}

Multi-Order Batching

Batching multiple orders for a single driver trip is essential for efficiency. A driver picking up from the same restaurant can deliver 2-3 orders in one trip, reducing dead time and increasing earnings.

Batching FactorConstraint
Maximum orders per batch3 (varies by market)
Maximum detour time8 minutes additional per order
Restaurant overlapMust be within 500m of each other
Delivery zone overlapOrders must be heading in similar direction
Temperature compatibilityHot and cold items can't be batched beyond 15 min
Customer preferencePremium customers can opt out of batching

11. Real-Time Driver Location Tracking

GPS tracking is the backbone of the real-time delivery experience. The system ingests millions of location updates per minute, processes them for accuracy, and broadcasts them to customers tracking their orders.

GPS Data Pipeline

graph LR
    A["Driver App
(GPS every 10s)"] --> B["Location Ingestion
API (Kafka Producer)"] B --> C["Kafka Topic
driver-locations"] C --> D["Location Processing
Worker"] D --> E["Redis GEO
(Real-time Position)"] D --> F["PostgreSQL
(Location History)"] D --> G["WebSocket
Broadcast"] G --> H["Customer App
(Map Update)"] G --> I["ETA Service
(Recalculate)"]

Location Processing

C#
public class LocationProcessingWorker : KafkaConsumer
{
    private readonly IRedisGeoStore _geoStore;
    private readonly IWebSocketBroadcaster _ws;
    private readonly IEtaCalculator _eta;
    private readonly ILocationFilter _filter;

    protected override async Task ProcessMessageAsync(
        DriverLocationUpdate update)
    {
        // 1. Filter out GPS noise and jitter
        var smoothed = _filter.ApplyKalmanFilter(
            update.Latitude, update.Longitude,
            update.Accuracy, update.Speed);

        if (smoothed == null) return; // Skip noisy points

        // 2. Update real-time position in Redis GEO
        await _geoStore.UpdateAsync(
            update.DriverId,
            smoothed.Latitude,
            smoothed.Longitude);

        // 3. Store for analytics and route verification
        await _locationHistory.AppendAsync(new LocationPoint
        {
            DriverId = update.DriverId,
            Latitude = smoothed.Latitude,
            Longitude = smoothed.Longitude,
            Speed = update.Speed,
            Heading = update.Heading,
            Timestamp = DateTime.UtcNow
        });

        // 4. Broadcast to customers tracking this driver
        var activeOrders = await _orders
            .GetActiveOrdersForDriverAsync(update.DriverId);

        foreach (var order in activeOrders)
        {
            // Recalculate ETA based on new position
            var newEta = await _eta.CalculateAsync(
                new Position(
                    smoothed.Latitude, smoothed.Longitude),
                order.Restaurant.Location,
                order.DeliveryAddress,
                update.Speed);

            await _ws.BroadcastAsync(order.CustomerId,
                new DriverLocationMessage
                {
                    OrderId = order.Id,
                    DriverLocation = new Position(
                        smoothed.Latitude, smoothed.Longitude),
                    UpdatedEta = newEta,
                    Timestamp = DateTime.UtcNow
                });
        }
    }
}
Kalman Filtering: Raw GPS data is noisy — urban canyons, tunnels, and device quality cause position jumps. A Kalman filter smooths the trajectory by combining the GPS measurement with the driver's estimated velocity and acceleration. This prevents the driver icon from jumping erratically on the customer's map while maintaining responsiveness to actual movement.

12. Route Optimization & ETA Prediction

Accurate ETAs are one of the most important features of a food delivery platform. Customers rely on ETAs to plan their meals, and inaccurate ETAs erode trust. The ETA must account for restaurant prep time, driver pickup time, traffic conditions, and route complexity.

ETA Calculation Components

ComponentSourceWeight
Kitchen prep timeRestaurant's historical average + current backlog35%
Driver-to-restaurant timeGoogle Maps ETA from driver to restaurant20%
Pickup wait timeHistorical pickup times for this restaurant10%
Restaurant-to-customer timeGoogle Maps ETA with real-time traffic30%
Batching overheadAdditional stops in multi-order delivery5%

Machine Learning ETA Model

Rule-based ETAs are a starting point, but ML models significantly improve accuracy by learning from historical patterns. The model is retrained weekly on the latest delivery data.

C#
public class MlEtaPredictor
{
    private readonly IModelRunner _model;

    public async Task<TimeSpan> PredictEtaAsync(
        EtaFeatures features)
    {
        var input = new EtaModelInput
        {
            // Restaurant features
            RestaurantId = features.RestaurantId,
            RestaurantAvgPrepTime =
                features.RestaurantAvgPrepTime.TotalMinutes,
            RestaurantCurrentBacklog =
                features.ActiveOrdersAtRestaurant,
            RestaurantDayOfWeek = features.OrderTime.DayOfWeek,
            RestaurantHourOfDay = features.OrderTime.Hour,

            // Driver features
            DriverToRestaurantDistanceKm =
                features.DriverDistance,
            DriverAvgSpeed = features.DriverAvgSpeed,
            DriverAcceptanceRate = features.DriverAcceptanceRate,
            DriverCompletedDeliveries =
                features.DriverExperience,

            // Traffic features
            TrafficSeverity = features.TrafficLevel, // 0-1
            WeatherCondition = features.WeatherCode,
            IsHoliday = features.IsHoliday ? 1 : 0,

            // Order features
            ItemCount = features.ItemCount,
            IsBatchedOrder = features.IsBatched ? 1 : 0,
            BatchSize = features.BatchSize,

            // Location features
            PickupZone = features.RestaurantZone,
            DeliveryZone = features.CustomerZone,
            DistanceKm = features.TotalDistance
        };

        var prediction = await _model.PredictAsync(input);

        // Apply correction factor based on recent accuracy
        var correction = await _getCorrectionFactorAsync(
            features.RestaurantId, features.CustomerZone);

        return TimeSpan.FromMinutes(
            prediction.EtaMinutes * correction);
    }
}
ETA Accuracy Targets: The ML model achieves ±3 minutes P80 accuracy for deliveries under 30 minutes, and ±5 minutes P80 for longer deliveries. The system tracks ETA accuracy per restaurant, per zone, and per time of day, and recalibrates when accuracy drops below threshold.

13. Dynamic Pricing (Surge Pricing)

Dynamic pricing balances supply (drivers) and demand (orders) by adjusting delivery fees during peak periods. The goal is not to maximize revenue per order, but to ensure enough drivers are available so that all orders can be delivered within acceptable timeframes.

Surge Multiplier Calculation

C#
public class DynamicPricingService
{
    private readonly IDemandForecast _demand;
    private readonly ISupplyMonitor _supply;
    private readonly IPriceHistory _history;

    public async Task<decimal> GetSurgeMultiplierAsync(
        GeoPoint location, DateTime time)
    {
        // 1. Get demand forecast for this zone and time
        var demand = await _demand.ForecastAsync(
            location, time);

        // 2. Get current driver supply
        var supply = await _supply
            .GetAvailableDriversAsync(location, radiusKm: 5);

        // 3. Calculate demand-to-supply ratio
        var ratio = demand.ExpectedOrdersPerHour /
            Math.Max(1, supply.AvailableDrivers);

        // 4. Map ratio to surge multiplier
        // Normal: ratio ~1.0 → multiplier 1.0x
        // High demand: ratio ~2.0 → multiplier 1.5x
        // Extreme: ratio ~4.0+ → multiplier 2.5x (cap)
        var multiplier = CalculateMultiplier(ratio);

        // 5. Apply smoothing (no sudden jumps)
        var previousMultiplier = await _history
            .GetRecentMultiplierAsync(location);
        multiplier = SmoothTransition(
            previousMultiplier, multiplier);

        // 6. Hard caps
        multiplier = Math.Max(1.0m,
            Math.Min(2.5m, multiplier));

        // 7. Log for monitoring
        await _history.RecordAsync(new SurgeRecord
        {
            Location = location,
            Time = time,
            Demand = demand,
            Supply = supply,
            Ratio = ratio,
            Multiplier = multiplier
        });

        return multiplier;
    }

    private decimal CalculateMultiplier(decimal ratio)
    {
        // Piecewise linear function
        if (ratio <= 1.0m) return 1.0m;
        if (ratio <= 1.5m) return 1.0m + (ratio - 1.0m) * 0.6m;
        if (ratio <= 2.5m) return 1.3m + (ratio - 1.5m) * 0.7m;
        if (ratio <= 4.0m) return 2.0m + (ratio - 2.5m) * 0.33m;
        return 2.5m; // Cap
    }
}

Surge Pricing Rules

Demand/Supply RatioSurge MultiplierEffect
< 0.81.0x (no surge)Normal delivery fee
0.8 - 1.21.0xBalanced market
1.2 - 1.81.2x - 1.5xModerate surge, attracts more drivers
1.8 - 3.01.5x - 2.0xHigh surge, significant driver incentive
> 3.02.0x - 2.5x (capped)Maximum surge, premium pricing shown to customer
Transparency is Key: Always show the surge multiplier to the customer before they place the order. Customers who see "Delivery fee: $5.99 (1.5x due to high demand)" are far less likely to abandon their cart than those who discover an inflated fee at checkout. Surge pricing should be visible, explained, and capped.

14. Payment Processing & Split Payments

Payment processing in food delivery is uniquely complex because a single customer transaction must be split among multiple parties: the restaurant, the delivery driver, and the platform. The system must handle holds, captures, refunds, and dispute resolution while maintaining financial accuracy.

Payment Flow

sequenceDiagram
    participant C as Customer
    participant PS as Payment Service
    participant S as Stripe/Adyen
    participant DB as Database
    participant K as Kafka

    Note over C,K: At Order Placement
    C->>PS: Authorize $35.00
    PS->>S: PaymentIntent (capture: manual)
    S-->>PS: Authorization hold
    PS->>DB: Store auth_id, status: HELD
    PS-->>C: Payment authorized

    Note over C,K: At Delivery (45 min later)
    PS->>S: Capture $35.00
    S-->>PS: Funds captured
    PS->>DB: status: CAPTURED
    PS->>K: PaymentCaptured Event

    Note over C,K: Next business day payout
    PS->>K: PayoutBatch Event
    Note right of PS: Restaurant: $22.00 (63%)<br/>Driver: $7.74 (22%)<br/>Platform: $5.26 (15%)

Split Payment Calculator

C#
public class PaymentSplitCalculator
{
    public PaymentSplit CalculateSplit(
        Order order, PricingResult pricing)
    {
        var subtotal = pricing.Subtotal;
        var deliveryFee = pricing.DeliveryFee;
        var serviceFee = pricing.ServiceFee;
        var tip = pricing.Tip;
        var platformCommissionRate = 0.30m; // 30% of subtotal

        return new PaymentSplit
        {
            RestaurantShare = new PartyPayment
            {
                Party = PaymentParty.Restaurant,
                Amount = subtotal - (subtotal * platformCommissionRate),
                Description = $"Food subtotal minus " +
                    $"{platformCommissionRate:P0} commission"
            },
            DriverShare = new PartyPayment
            {
                Party = PaymentParty.Driver,
                Amount = deliveryFee + tip,
                Description = $"Delivery fee ${deliveryFee} " +
                    $"+ Tip ${tip}"
            },
            PlatformShare = new PartyPayment
            {
                Party = PaymentParty.Platform,
                Amount = (subtotal * platformCommissionRate)
                    + serviceFee,
                Description = $"Commission " +
                    $"${subtotal * platformCommissionRate:F2} " +
                    $"+ Service fee ${serviceFee}"
            },
            TotalCaptured = pricing.TotalAmount,
            // Verify: all shares sum to total
            Validation = ValidateSplit(
                subtotal, deliveryFee, serviceFee, tip,
                platformCommissionRate)
        };
    }
}

Refund Handling

Refunds are common in food delivery (missing items, wrong orders, quality issues). The system supports three refund types:

  • Full refund: Order never delivered. Entire amount returned to customer. Restaurant and driver portions clawed back from next payout.
  • Partial refund: Specific items missing or incorrect. Only those items' prices are refunded. The restaurant is debited for the refunded items.
  • Credit refund: Platform credits added to customer's account for future orders. This avoids payment processing fees and encourages repeat business.
Financial Reconciliation: Daily reconciliation jobs verify that: (1) sum of all captures equals sum of all authorizations, (2) sum of all splits equals the total captured amount, (3) payouts to restaurants and drivers match the calculated shares minus any clawbacks. Discrepancies trigger alerts to the finance team.

15. Push Notifications & SMS

Notifications are the glue that keeps all three parties informed throughout the delivery lifecycle. The system sends push notifications via APNs/FCM and SMS via Twilio for critical updates.

Notification Events

EventRecipientChannelTemplate
Order placedCustomerPush + Email"Your order from {restaurant} has been placed!"
Order confirmedCustomerPush"{restaurant} confirmed your order. Preparing now."
Order readyDriverPush + SMS"Order #{id} ready for pickup at {restaurant}"
Driver assignedCustomerPush"{driver} is picking up your order. ETA: {time}"
Driver arrivingCustomerPush + SMS"{driver} is almost there! 2 minutes away."
Order deliveredCustomerPush"Your order has been delivered. Enjoy!"
PromotionCustomerPush"{promo_text}"

Notification Service

C#
public class NotificationService
{
    private readonly IPushProvider _push; // APNs + FCM
    private readonly ISmsProvider _sms;   // Twilio
    private readonly IEmailProvider _email;
    private readonly IUserPreferences _prefs;

    public async Task SendAsync(Notification notification)
    {
        var preferences = await _prefs
            .GetAsync(notification.RecipientUserId);

        // Check user notification preferences
        if (!preferences.IsEnabled(notification.Type))
            return;

        var tasks = new List<Task>();

        if (preferences.PushEnabled)
        {
            tasks.Add(_push.SendAsync(new PushMessage
            {
                Token = notification.RecipientPushToken,
                Title = notification.Title,
                Body = notification.Body,
                Data = notification.Payload,
                Badge = notification.BadgeCount,
                Sound = "default"
            }));
        }

        if (preferences.SmsEnabled &&
            notification.Priority == Priority.Critical)
        {
            tasks.Add(_sms.SendAsync(new SmsMessage
            {
                To = notification.RecipientPhone,
                Body = notification.Body
            }));
        }

        if (preferences.EmailEnabled &&
            notification.Type == NotificationType.OrderConfirmation)
        {
            tasks.Add(_email.SendAsync(new EmailMessage
            {
                To = notification.RecipientEmail,
                Subject = notification.Title,
                Body = notification.HtmlBody
            }));
        }

        await Task.WhenAll(tasks);

        // Log notification for analytics
        await LogNotificationAsync(notification);
    }
}

16. Customer Support & Issue Resolution

Customer support is an integral part of the food delivery experience. Issues like missing items, wrong orders, late deliveries, and food quality problems are common and must be resolved quickly to maintain customer trust.

Issue Categories and Resolution Flow

graph TB
    A["Customer Reports Issue"] --> B{"Issue Category"}
    B -->|Missing Items| C["Check Order Contents
vs Restaurant Items"] B -->|Wrong Order| D["Compare Delivered
vs Ordered"] B -->|Late Delivery| E["Check Driver Route
and ETA History"] B -->|Quality Issue| F["Photo Evidence
+ Restaurant Rating"] C --> G{"Auto-Resolution
Eligible?"} D --> G E --> G F --> H["Manual Review
by Support Agent"] G -->|Yes| I["Auto-refund
(up to $20)"] G -->|No| J["Support Agent
Review Queue"] J --> K["Agent Resolution
(refund, credit, or deny)"] I --> L["Customer Notified"] K --> L

Auto-Resolution Engine

C#
public class AutoResolutionEngine
{
    private readonly IOrderRepository _orders;
    private readonly IRefundService _refunds;

    public async Task<ResolutionResult> TryAutoResolveAsync(
        SupportTicket ticket)
    {
        // Auto-resolution eligible criteria
        var eligible = ticket.Type switch
        {
            IssueType.MissingItems =>
                ticket.RefundAmount <= 20m &&
                ticket.CustomerHistory.TotalOrders > 5 &&
                ticket.CustomerHistory.PreviousRefunds < 3,

            IssueType.LateDelivery =>
                ticket.DelayMinutes > 15 &&
                ticket.RefundAmount <= 10m,

            IssueType.WrongItems =>
                ticket.RefundAmount <= 30m &&
                ticket.HasPhotoEvidence,

            _ => false
        };

        if (!eligible)
            return new ResolutionResult
            {
                Resolved = false,
                Reason = "Requires manual review"
            };

        // Process auto-refund
        var refund = await _refunds.ProcessAsync(
            new RefundRequest
            {
                OrderId = ticket.OrderId,
                Amount = ticket.RefundAmount,
                Reason = ticket.Type.ToString(),
                Type = RefundType.Partial,
                ProcessedBy = "auto-resolution"
            });

        // Close ticket
        ticket.Status = TicketStatus.Resolved;
        ticket.Resolution = $"Auto-resolved: " +
            $"${ticket.RefundAmount:F2} refunded";
        ticket.ResolvedAt = DateTime.UtcNow;

        return new ResolutionResult
        {
            Resolved = true,
            RefundAmount = ticket.RefundAmount,
            Message = "Refund processed. " +
                "You'll see it in 3-5 business days."
        };
    }
}
Fraud Detection in Support: The system tracks refund patterns per customer. Customers who request refunds more than 15% of the time are flagged for manual review. Common fraud patterns include: reporting missing items on every order, claiming wrong orders when tracking shows correct delivery, and creating multiple accounts for refund abuse.

17. Rating & Review System

Two-sided ratings maintain quality across the platform. Customers rate restaurants and drivers; restaurants can rate customers (for behavior, not food). Ratings directly affect search ranking, driver matching, and restaurant visibility.

Rating Data Model

C#
public class Rating
{
    public Guid Id { get; set; }
    public Guid OrderId { get; set; }
    public RatingDirection Direction { get; set; } // ToRestaurant, ToDriver, ToCustomer
    public Guid FromUserId { get; set; }
    public Guid ToUserId { get; set; }
    public int Score { get; set; } // 1-5
    public List<RatingTag> Tags { get; set; }
    public string Comment { get; set; }
    public DateTime CreatedAt { get; set; }
}

// Pre-defined rating tags for structured feedback
public static class RatingTags
{
    public static readonly string[] RestaurantPositive =
        { "Great food", "Fast prep", "Well packaged", "Accurate order" };
    public static readonly string[] RestaurantNegative =
        { "Missing items", "Cold food", "Wrong items", "Slow prep" };
    public static readonly string[] DriverPositive =
        { "Friendly", "Fast delivery", "Careful with food", "Good communication" };
    public static readonly string[] DriverNegative =
        { "Late", "Rude", "Messy handling", "Wrong address" };
}

Rating Impact on Platform

Impact AreaHow Ratings Affect It
Search rankingRestaurants below 4.0 stars are demoted; above 4.5 get a boost
Driver matchingDrivers below 4.2 stars receive fewer premium order offers
Restaurant visibilityRestaurants below 3.8 stars may be hidden from search results
Driver deactivationDrivers below 4.0 stars after 50+ deliveries are reviewed for deactivation
Customer trust scoresCustomers with low ratings from restaurants may be flagged for priority support review

Rating Aggregation Algorithm

C#
public class RatingAggregator
{
    // Bayesian average prevents new restaurants with few
    // ratings from dominating the rankings
    public decimal CalculateBayesianAverage(
        decimal restaurantAvgRating,
        int restaurantRatingCount,
        decimal globalAvgRating = 3.8m,
        int minRatingsRequired = 10)
    {
        // Weighted average pulling toward global mean
        // until enough ratings are collected
        var weightedRating =
            (minRatingsRequired * globalAvgRating)
            + (restaurantRatingCount * restaurantAvgRating);
        var weightedCount =
            minRatingsRequired + restaurantRatingCount;

        return weightedRating / weightedCount;
    }
}

18. Promotions, Coupons & Loyalty Program

Promotions are a critical growth lever for food delivery platforms. The system must support various promotion types while preventing abuse and accurately tracking ROI.

Promotion Types

TypeDescriptionExample
Percentage discountOff entire order or specific items"20% off your first order, max $15"
Free deliveryWaive delivery fee"Free delivery on orders over $25"
Fixed amount offDollar amount deducted from total"$5 off orders of $30+"
BOGOBuy one get one free"Buy 1 entree, get 1 free"
Loyalty pointsEarn points per dollar spent"Earn 1 point per $1 spent"
Referral bonusCredit for referring new users"Give $10, get $10"
Subscription discountMonthly subscription for perks"DashPass: $9.99/mo for free delivery"

Promo Code Validation

C#
public class PromoValidationEngine
{
    private readonly IPromoRepository _promos;
    private readonly IUsageTracker _usage;
    private readonly IFraudDetector _fraud;

    public async Task<PromoResult> ValidateAsync(
        string code, Guid customerId, OrderContext order)
    {
        var promo = await _promos.GetByCodeAsync(code);
        if (promo == null)
            return PromoResult.Invalid("Invalid promo code");

        // Check expiration
        if (promo.ExpiresAt < DateTime.UtcNow)
            return PromoResult.Invalid("Promo code expired");

        // Check minimum order amount
        if (order.Subtotal < promo.MinimumOrderAmount)
            return PromoResult.Invalid(
                $"Minimum order: ${promo.MinimumOrderAmount}");

        // Check usage limits
        var usageCount = await _usage
            .GetUsageCountAsync(code, customerId);
        if (usageCount >= promo.MaxUsesPerCustomer)
            return PromoResult.Invalid(
                "Promo code already used");

        // Check new customer only
        if (promo.NewCustomersOnly &&
            await _usage.HasOrderHistoryAsync(customerId))
            return PromoResult.Invalid(
                "For new customers only");

        // Fraud check
        var fraudCheck = await _fraud.CheckPromoAbuseAsync(
            customerId, code);
        if (fraudCheck.IsSuspicious)
            return PromoResult.Invalid("Unable to apply promo");

        // Calculate discount
        var discount = CalculateDiscount(promo, order);

        return PromoResult.Valid(discount, promo);
    }
}

Loyalty Program Tiers

TierPoints RequiredBenefits
Bronze0 - 499Earn 1 pt/$1, birthday reward
Silver500 - 1,999Earn 1.5 pts/$1, free delivery 1x/month, priority support
Gold2,000 - 4,999Earn 2 pts/$1, free delivery 2x/month, exclusive deals
Platinum5,000+Earn 3 pts/$1, unlimited free delivery, VIP support, early access to new restaurants

19. Analytics Dashboard

The analytics platform provides actionable insights to restaurants, drivers, and internal teams. Data flows through a real-time streaming pipeline into ClickHouse for fast analytical queries.

Analytics Pipeline

graph LR
    A["Kafka Events
(all platform events)"] --> B["Flink
Stream Processing"] B --> C["ClickHouse
(OLAP)"] B --> D["Redis
(Real-time Counters)"] C --> E["Restaurant
Dashboard"] C --> F["Driver
Dashboard"] C --> G["Internal
BI Tools"] D --> E D --> F

Restaurant Analytics

MetricDescriptionTime Granularity
RevenueTotal sales, net of refunds and commissionsHourly, daily, weekly
Order volumeNumber of orders, average order valueHourly (peak analysis)
Preparation timeAverage time from order accepted to readyDaily (trend)
Menu performanceTop items, items with low conversion, modifier attach rateWeekly
Rating trendsAverage rating over time, common complaintsWeekly
Peak hoursHeatmap of order volume by hour and dayWeekly
Customer retentionRepeat order rate, customer lifetime valueMonthly

Driver Analytics

MetricDescription
Hourly earningsBase pay + tips + bonuses per active hour
Deliveries per hourEfficiency metric (target: 2-3 per hour)
Acceptance rate% of order offers accepted (affects matching score)
On-time rate% of deliveries within estimated time
Customer ratingsAverage rating and recent trend
Distance drivenTotal km driven vs. km with active delivery

20. Monitoring & Observability

A food delivery platform requires comprehensive monitoring because failures directly impact real-time operations. A driver tracking outage means customers can't see their orders. A payment failure means orders can't be placed. An ETA miscalculation means angry customers.

Key Dashboards

DashboardKey MetricsAlert Threshold
Order PipelineOrders/minute, failure rate, avg processing timeFailure rate > 0.1%
Driver TrackingGPS updates/sec, WebSocket connections, drop rateDrop rate > 2%
Payment ProcessingAuthorization success rate, capture failures, refund rateAuth failures > 1%
Search PerformanceQuery latency P95, index freshness, result relevanceP95 > 300ms
KDS ConnectivityActive connections, message delivery rateDelivery rate < 99.9%
ETA AccuracyPrediction vs actual, per-zone accuracyP80 deviation > 5 min

Distributed Tracing

C#
// Every request gets a trace ID for end-to-end tracking
public class OrderPlacementMiddleware
{
    public async Task InvokeAsync(HttpContext context)
    {
        var traceId = Guid.NewGuid().ToString("N");
        using var activity = Telemetry.StartActivity(
            "OrderPlacement");
        activity?.SetTag("trace.id", traceId);
        activity?.SetTag("customer.id",
            context.User.GetCustomerId());

        // Propagate trace ID through all service calls
        context.Items["TraceId"] = traceId;

        try
        {
            await _next(context);
            activity?.SetTag("order.status", "success");
        }
        catch (Exception ex)
        {
            activity?.SetTag("order.status", "failed");
            activity?.SetTag("error.type", ex.GetType().Name);

            // Alert if order failure rate spikes
            await _metrics.IncrementAsync(
                "orders.failed",
                new Dictionary<string, string>
                {
                    ["error_type"] = ex.GetType().Name,
                    ["trace_id"] = traceId
                });
            throw;
        }
    }
}
Incident Response: During a Friday dinner rush, even a 5-minute outage can cost millions in lost revenue and customer trust. The platform maintains a runbook for every critical path failure: payment service down → fall through to retry with backup processor; KDS offline → fall back to SMS notifications; driver tracking down → pause new order dispatch until tracking recovers.

Alerting Rules

Alert fatigue is a real problem — if everything is urgent, nothing is. The platform uses a three-tier alerting system based on business impact. P1 (critical) alerts trigger immediate page to on-call engineer and auto-rollback if possible. Examples: order failure rate exceeds 1% for 2 minutes, payment authorization success drops below 98%, or more than 5% of WebSocket connections drop simultaneously. P2 (high) alerts send Slack notification and create a ticket. Examples: search P95 latency exceeds 300ms for 5 minutes, driver GPS ingestion lag exceeds 10 seconds, or KDS message delivery rate drops below 99.5%. P3 (low) alerts are dashboard-only, reviewed daily. Examples: individual restaurant KDS disconnections, ETA accuracy degrades by more than 1 minute from baseline, or cache hit ratio drops below 85%.

SLA Tracking

SLATargetMeasurement
Order placement success rate99.99%Successful orders / attempted orders per minute
Search availability99.99%Search queries returning results / total queries
Driver GPS availability99.95%Active WebSocket connections / total active drivers
Payment success rate99.9%Successful authorizations / total attempts
ETA accuracy±3 min (P80)Prediction vs actual delivery time per order
Notification delivery rate99.5%Successfully delivered push / total sent

21. Security & Fraud Prevention

A food delivery platform handles payment data, personal addresses, and financial transactions — making it a high-value target for fraud and attacks.

Threat Model

ThreatAttack VectorImpactMitigation
Payment fraudStolen credit cardsChargebacks, financial loss3D Secure, velocity checks, device fingerprinting
Refund abuseMultiple accounts claiming missing itemsRevenue lossRefund rate tracking, account linkage detection
Promo abuseCreating accounts for new customer promosPromo budget drainDevice fingerprint, phone number verification
Driver fraudMarking orders delivered without pickupDriver pay fraudGPS verification, photo proof of delivery
Data breachAddress/payment data exposureRegulatory fines, trust lossEncryption at rest, PCI DSS compliance, tokenization
Account takeoverCredential stuffingUnauthorized ordersMFA, device trust, behavioral analysis

Fraud Detection Service

C#
public class FraudDetectionService
{
    public async Task<FraudRisk> AssessOrderRiskAsync(
        Order order, Customer customer)
    {
        var riskScore = 0m;
        var signals = new List<RiskSignal>();

        // 1. Check order value against customer history
        var avgOrder = await _analytics
            .GetAverageOrderValueAsync(customer.Id);
        if (order.TotalAmount > avgOrder * 3)
        {
            riskScore += 25m;
            signals.Add(new RiskSignal
            {
                Type = "high_order_value",
                Detail = $"Order ${order.TotalAmount} " +
                    $"vs avg ${avgOrder}"
            });
        }

        // 2. Check address distance from usual
        var usualAddress = await _addresses
            .GetMostUsedAsync(customer.Id);
        if (usualAddress != null)
        {
            var distance = GeoCalculator
                .DistanceKm(usualAddress.Location,
                    order.DeliveryAddress.Location);
            if (distance > 20) // 20km from usual
            {
                riskScore += 20m;
                signals.Add(new RiskSignal
                {
                    Type = "unusual_location",
                    Detail = $"{distance}km from usual address"
                });
            }
        }

        // 3. Check recent refund rate
        var refundRate = await _analytics
            .GetRefundRateAsync(customer.Id, days: 30);
        if (refundRate > 0.15m) // 15% refund rate
        {
            riskScore += 30m;
            signals.Add(new RiskSignal
            {
                Type = "high_refund_rate",
                Detail = $"{refundRate:P0} refund rate"
            });
        }

        // 4. Check device fingerprint
        var deviceRisk = await _deviceService
            .CheckDeviceRiskAsync(order.DeviceFingerprint);
        if (deviceRisk.IsKnownFraudDevice)
        {
            riskScore += 40m;
            signals.Add(new RiskSignal
            {
                Type = "fraud_device",
                Detail = deviceRisk.DeviceId
            });
        }

        return new FraudRisk
        {
            Score = Math.Min(100m, riskScore),
            Level = riskScore < 30 ? RiskLevel.Low
                : riskScore < 60 ? RiskLevel.Medium
                : RiskLevel.High,
            Signals = signals,
            Action = riskScore >= 60
                ? FraudAction.BlockAndReview
                : riskScore >= 30
                    ? FraudAction.FlagForReview
                    : FraudAction.Allow
        };
    }
}

22. Compliance (Food Safety & Gig Worker Regulations)

Food delivery platforms operate in a complex regulatory environment spanning food safety, labor law, data privacy, and financial regulations.

Regulatory Requirements

AreaRegulationPlatform Responsibility
Food SafetyLocal health department regulationsVerify restaurants have valid permits, track food handling compliance
Data PrivacyGDPR, CCPAData minimization, right to deletion, consent management for location tracking
Gig WorkerAB5 (California), EU Platform Workers DirectiveClassify drivers correctly, provide benefits where required
FinancialPCI DSS, PSD2Secure payment processing, SCA for EU transactions
Tax1099 reporting (US), VAT (EU)Report driver earnings, collect and remit applicable taxes
AccessibilityADA, WCAG 2.1Accessible app design for customers with disabilities
Non-Negotiable: Compliance violations can result in fines of millions of dollars, platform shutdowns in entire cities, and permanent reputational damage. The compliance team has veto power over any feature that touches driver classification, customer data, or food safety information.

Data Retention Policies

Different data types have different retention requirements. Customer personal data (names, addresses, payment methods) is retained while the account is active and deleted within 30 days of account closure per GDPR Article 17. Order transaction data is retained for 7 years for tax and accounting compliance. Driver location data is retained for 90 days for dispute resolution and then aggregated into anonymized route analytics. Support ticket transcripts are retained for 3 years for quality assurance and training. Payment card data is never stored directly — the platform uses Stripe tokenization so PCI DSS scope is minimized to the payment processing layer. All data retention and deletion is automated through lifecycle policies in the database and object storage layers.

Driver Classification Considerations

The classification of delivery drivers as independent contractors versus employees is one of the most consequential legal questions facing food delivery platforms. Different jurisdictions have different tests: the ABC test used in California (AB5) presumes workers are employees unless the company can prove (A) they are free from control, (B) they perform work outside the usual course of hiring entity's business, and (C) they are customarily engaged in an independently established trade. The platform must track and adapt to evolving regulations. In practice, this means the system must support flexible driver configurations: some markets require minimum hourly guarantees, others require healthcare stipends, and some mandate worker's compensation insurance. The driver payment and scheduling system must be configurable per jurisdiction to comply with these varying requirements.

23. Cost Estimation

Estimating infrastructure costs for a food delivery platform serving 30M monthly active users across 6,000 cities.

Monthly Infrastructure Costs

ServiceSpecificationMonthly Cost
API Gateway (Kong/Envoy)10 instances, handling 15K QPS$4,000
Order Service (K8s)20 pods, 4 vCPU, 8GB RAM each$8,000
Search Service (Elasticsearch)5-node cluster, 2TB data$6,500
PostgreSQL (Orders, Users)Multi-AZ, r6g.2xlarge, 3 replicas$7,000
Redis Cluster (Realtime)6-node cluster, 128GB total$4,200
Apache Kafka6 brokers, 3TB storage$5,500
ClickHouse (Analytics)3-node cluster, 6TB data$4,800
WebSocket Gateway10 instances, 2M concurrent$3,000
CDN (CloudFront)50TB/month transfer$4,500
S3 (Images, Receipts)50TB stored$1,200
Monitoring (Datadog/Grafana)Full stack observability$5,000
Maps API (Google/Mapbox)50M requests/day$12,000
Payment Processing (Stripe)2.9% + $0.30 per transaction~$150K on $5M GMV/day
SMS (Twilio)5M SMS/month$2,500
Total (excl. payment fees)~$66,200
Cost Optimization: Reserved instances for stable workloads (PostgreSQL, Kafka) save 40% vs. on-demand. Spot instances for analytics batch jobs save 70%. Caching aggressively reduces database load and costs — a well-tuned Redis cache can eliminate 80% of PostgreSQL reads for menu and restaurant data.

24. API Design

The API layer follows RESTful conventions with gRPC for internal service-to-service communication. All public APIs are versioned, rate-limited, and authenticated.

Core API Endpoints

MethodEndpointDescription
GET/api/v1/restaurants?lat={lat}&lng={lng}Search nearby restaurants
GET/api/v1/restaurants/{id}/menuGet restaurant menu
POST/api/v1/ordersPlace an order
GET/api/v1/orders/{id}Get order status and details
PATCH/api/v1/orders/{id}/cancelCancel an order
GET/api/v1/orders/{id}/trackGet real-time tracking (WebSocket upgrade)
POST/api/v1/cartCreate/update cart
POST/api/v1/promos/validateValidate a promo code
POST/api/v1/support/ticketsCreate support ticket
POST/api/v1/ratingsSubmit a rating

API Request/Response Example

JSON
// POST /api/v1/orders
// Request
{
    "restaurant_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "items": [
        {
            "menu_item_id": "item_001",
            "quantity": 2,
            "modifiers": [
                { "group": "Size", "selection": "Large" },
                { "group": "Extras", "selection": "Extra Cheese" }
            ],
            "special_instructions": "No onions please"
        }
    ],
    "delivery_address": {
        "line1": "123 Main Street",
        "apartment": "4B",
        "city": "New York",
        "state": "NY",
        "zip": "10001",
        "lat": 40.7128,
        "lng": -74.0060
    },
    "payment_method_id": "pm_stripe_abc123",
    "tip": 5.00,
    "promo_code": "WELCOME20",
    "idempotency_key": "ord_unique_key_12345"
}

// Response (202 Accepted)
{
    "order_id": "ord_9f8e7d6c-5b4a-3210-fedc-ba9876543210",
    "status": "placed",
    "estimated_delivery": "2026-07-12T19:45:00Z",
    "pricing": {
        "subtotal": 28.00,
        "tax": 2.52,
        "delivery_fee": 3.99,
        "service_fee": 3.36,
        "discount": -5.60,
        "tip": 5.00,
        "total": 37.27
    }
}

Rate Limiting and Throttling

The API gateway enforces rate limits per client to prevent abuse and ensure fair usage. Authenticated customer apps are limited to 100 requests per minute per user. Restaurant KDS connections are limited to 30 requests per minute (mostly order status updates). Driver apps are limited to 60 requests per minute (location updates are sent through a separate ingestion endpoint). Internal service-to-service calls use a separate rate limit tier of 10,000 requests per minute per service. The rate limiter uses a sliding window algorithm implemented in Redis, which provides accurate counts even across multiple gateway instances. When a client exceeds their limit, the API returns HTTP 429 with a Retry-After header. Critical endpoints like order placement have a separate burst allowance — clients can exceed their steady-state rate by 3x for up to 10 seconds, which handles legitimate spikes when a user retries a failed order.

API Versioning Strategy

The platform uses URI-based versioning (/api/v1/, /api/v2/) for breaking changes and header-based versioning for minor additions. When a new version is released, the previous version enters a deprecation period of 6 months. During this period, deprecated endpoints return a Sunset header indicating the removal date. The mobile apps use feature flags to gradually migrate users to new API versions. This approach ensures that older app versions continue to work while new features are rolled out progressively. Backward-compatible changes (adding new response fields, new optional request parameters) are made within the current version without requiring a version bump.

25. Testing Strategy

Testing a food delivery platform requires multiple layers: unit tests for business logic, integration tests for service interactions, contract tests for API boundaries, and load tests for peak traffic.

Testing Pyramid

Test TypeScopeTarget CoverageTools
Unit testsPricing engine, state machine, validators90%+xUnit, Moq
Integration testsService + database interactions80%+Testcontainers, WebApplicationFactory
Contract testsAPI request/response schemasAll public endpointsPact
E2E testsFull order lifecycleCritical pathsPlaywright, custom fixtures
Load testsPeak traffic simulation15K QPS sustainedk6, Gatling
Chaos testsFailure scenariosAll critical pathsLitmus, Chaos Mesh

Integration Test Example

C#
[Fact]
public async Task PlaceOrder_ValidRequest_CreatesOrderAndAuthorizesPayment()
{
    // Arrange
    await using var fixture = new OrderServiceTestFixture();
    var restaurant = await fixture.SeedRestaurant(
        menuItems: new[] { new MenuItem { Id = "item_001", Price = 14.00m } });
    var customer = await fixture.SeedCustomer();
    var request = new PlaceOrderRequest
    {
        CustomerId = customer.Id,
        RestaurantId = restaurant.Id,
        Items = new[] { new OrderItem
            { MenuItemId = "item_001", Quantity = 2 } },
        DeliveryAddress = customer.DefaultAddress,
        PaymentMethodId = "test_pm_valid",
        Tip = 5.00m,
        IdempotencyKey = Guid.NewGuid().ToString()
    };

    // Act
    var result = await fixture.OrderService.PlaceOrderAsync(request);

    // Assert
    Assert.NotEqual(Guid.Empty, result.OrderId);
    Assert.Equal(OrderStatus.Placed, result.Status);

    var order = await fixture.Db.Orders
        .Include(o => o.Items)
        .FirstAsync(o => o.Id == result.OrderId);
    Assert.Equal(28.00m, order.Subtotal);
    Assert.Equal(2, order.Items.Count);

    // Verify payment authorization was made
    fixture.PaymentGatewayMock.Verify(pg =>
        pg.AuthorizeAsync(It.IsAny<PaymentAuthorization>(),
            It.IsAny<CancellationToken>()),
        Times.Once);

    // Verify event was published
    fixture.EventBusMock.Verify(eb =>
        eb.PublishAsync(It.IsAny<OrderPlacedEvent>(),
            It.IsAny<CancellationToken>()),
        Times.Once);
}

[Fact]
public async Task PlaceOrder_AfterRestaurantCloses_ReturnsUnavailableError()
{
    await using var fixture = new OrderServiceTestFixture();
    var restaurant = await fixture.SeedRestaurant(
        closingTime: DateTime.UtcNow.AddMinutes(-30));

    var request = BuildTestRequest(restaurant.Id);

    await Assert.ThrowsAsync<RestaurantUnavailableException>(
        () => fixture.OrderService.PlaceOrderAsync(request));
}

Load Test Scenarios

ScenarioTargetSuccess Criteria
Friday dinner rush15K orders/secP99 latency < 500ms, 0% errors
Search during peak50K queries/secP95 latency < 200ms
GPS ingestion surge50K events/secAll events processed within 2 seconds
Payment burst8K authorizations/secP99 latency < 2 seconds
Database failoverPrimary downFailover in < 30s, zero order loss
Testing Philosophy: Every bug that reaches production during dinner rush can cost $100K+ in lost orders and customer churn. The team maintains a 72-hour code freeze before major holidays (Thanksgiving, Super Bowl Sunday, New Year's Eve) when traffic peaks and failures are most costly.

26. Interview Q&A Deep Dive

Q1: How do you handle the case where a restaurant accepts an order but then runs out of ingredients?

Answer: The restaurant can decline individual items through the KDS before confirming the full order. If items are declined, the customer is notified and given three options: (1) accept a substitute suggested by the restaurant, (2) remove the item and receive a partial refund for it, or (3) cancel the entire order for a full refund. If the restaurant confirms the order but later cannot fulfill it, the restaurant must manually cancel, which triggers a full refund and assigns the restaurant a cancellation penalty. Cancellation rates above 5% trigger a review of the restaurant's availability management practices.

Q2: How do you prevent a driver from accepting multiple orders that exceed their capacity?

Answer: Each driver has a configurable maximum batch size (default: 3 orders). The matching service checks the driver's current active orders before offering a new batch. The batch optimizer also considers physical constraints: bag capacity (insulated bags can hold 2-3 orders), temperature requirements (hot and cold items need separate bags), and vehicle type (bicycle couriers have lower capacity than car couriers). When the driver accepts a batch, the system reserves their capacity so no additional orders are offered until one is delivered.

Q3: How do you handle real-time inventory when a restaurant marks an item as sold out while customers are adding it to their carts?

Answer: This is the classic inventory race condition. The solution is optimistic concurrency with a two-phase approach: (1) At cart display time, items are shown as available based on a 30-second cache. (2) At order placement time, the system re-validates all items against the current availability status. If an item became unavailable between cart and checkout, the customer is notified and must remove or substitute the item before proceeding. This is the same pattern used by e-commerce platforms for flash sales. The 30-second staleness window is acceptable because: restaurants rarely sell out in that window, and the re-validation at checkout catches any discrepancies.

Q4: How would you design the system to handle 100K concurrent orders during a major event (e.g., Super Bowl Sunday)?

Answer: The system needs several scaling mechanisms: (1) Database: shard orders by restaurant_id so each shard handles a subset of the traffic. Use read replicas for search and tracking queries. (2) Kafka: pre-provision partitions based on peak estimates. Each restaurant topic partition handles ~5K events/sec. (3) Order processing: queue orders at the API gateway with a virtual waiting room. Display estimated wait time to users. Process orders in FIFO within each restaurant's queue. (4) Payment processing: use Stripe's high-throughput mode with batch authorizations. Pre-authorize in batches of 100 and capture individually. (5) Driver matching: increase search radius and apply surge pricing to attract more drivers. Enable multi-order batching aggressively.

Q5: How do you ensure ETA accuracy when a restaurant's prep time varies significantly?

Answer: The ETA model uses three layers of prep time estimation: (1) Restaurant's historical average prep time for similar orders (weight: 30%). (2) Real-time signal: number of active orders at the restaurant right now, which indicates current kitchen load (weight: 40%). (3) Recent deviation: how much the restaurant has been over/under their stated prep times in the last hour (weight: 30%). The model is per-restaurant and retrained weekly. Additionally, the system allows restaurants to update prep times through the KDS, and these manual adjustments override the model. The key insight is that prep time is the single largest source of ETA error — delivery time is relatively predictable with maps data, but kitchen timing is inherently uncertain.

Q6: How do you handle the payment flow when an order is cancelled after payment was authorized but before delivery?

Answer: The payment flow uses a two-phase commit pattern: (1) At order placement: authorize (hold) the full amount on the customer's card. No money moves yet. (2) At delivery: capture the held amount. Money is transferred. For cancellations: If cancelled before restaurant confirmation: void the authorization (money never leaves the customer). If cancelled after restaurant confirmation but before capture: void the authorization plus charge the restaurant a cancellation fee. If cancelled after delivery (refund): capture first, then issue a refund. The captured amount takes 3-5 business days to return to the customer. Restaurant portion is clawed back from their next payout. The system maintains an audit trail of every authorization, capture, void, and refund for financial reconciliation.

Q7: How do you handle delivery to apartment buildings where the driver can't reach the door?

Answer: The delivery address model supports multiple delivery modes: (1) Door delivery: driver brings to the customer's door (default for houses). (2) Lobby/entrance: driver leaves at building entrance (common for apartments without access). (3) Meet at vehicle: customer comes to the driver's car (used for secure buildings). (4) Hand off to doorman: for buildings with concierge service. The customer specifies their preferred mode when placing the order. The driver app shows the delivery mode and provides in-app messaging to coordinate handoff. For "leave at door" orders (post-pandemic standard), the driver takes a photo of the delivered order as proof of delivery, which is stored and accessible for dispute resolution.

Q8: How do you handle cross-platform consistency when the same restaurant is listed on multiple delivery platforms?

Answer: This is a real operational challenge. Restaurants often list on DoorDash, Uber Eats, and Grubhub simultaneously, each with different menus, pricing, and commission rates. Our platform doesn't control what restaurants list elsewhere — we focus on our own data accuracy. Key mechanisms: (1) Menu sync: restaurants manage their menu through our KDS. If they update prices or mark items sold out, it's reflected immediately on our platform. (2) Capacity awareness: if we detect a restaurant receiving high order volume across multiple platforms (via our order rate tracking), we proactively increase prep time estimates to prevent delays. (3) Relationship management: our restaurant success team works with high-volume restaurants to ensure they manage capacity across platforms. We also offer integration APIs for restaurants using third-party POS systems that manage multi-platform orders.

Key Numbers to Remember

MetricValue
Order placement latency (P99)< 500ms
Search latency (P95)< 200ms
GPS update frequencyEvery 10 seconds
Driver matching time< 30 seconds from order ready
ETA accuracy (P80)±3 minutes
Restaurant confirmation timeout15 minutes
Average delivery time25-35 minutes
Maximum batch size3 orders per driver trip
Payment hold duration48 hours (authorization window)
Refund auto-resolution threshold$20 per incident
Platform commission rate25-30% of subtotal
Search ranking refresh interval30 seconds
Menu cache TTL30 seconds
Peak QPS capacity15,000+
Monthly infrastructure cost~$66,200 (excl. payment fees)

Pre-Interview Checklist

  • Understand the three-sided marketplace dynamics (customer, restaurant, driver)
  • Know the order state machine and every valid transition
  • Explain the pricing engine breakdown (tax, fees, tips, promos)
  • Discuss driver matching algorithm and batch optimization
  • Know GPS data pipeline (Kalman filtering, real-time broadcast)
  • Explain ETA prediction (multi-factor model with ML)
  • Understand dynamic/surge pricing mechanics and transparency
  • Discuss split payment flow (authorize → capture → payout split)
  • Know how to handle scaling for peak events (Super Bowl, NYE)
  • Explain fraud detection for refunds and promo abuse
  • Discuss KDS integration and real-time restaurant communication
  • Understand the compliance landscape (food safety, gig worker, PCI)

Food Delivery Platform Design — Senior+ Guide | Ayodhyya