How to Design a Food Delivery Platform
A Comprehensive Senior+ Engineering Guide — From Order Placement to Last-Mile Delivery
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.
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
| Metric | Scale (Major Platform) |
|---|---|
| Daily active users | 30M+ |
| Orders per peak hour | 500K+ |
| Restaurants on platform | 800K+ |
| Active delivery drivers | 2M+ |
| Cities served | 6,000+ |
| Average delivery time | 25-35 minutes |
| GPS updates per driver per minute | 4-6 |
| Peak QPS (orders per second) | 15,000+ |
2. Requirements (Functional & Non-Functional)
Functional Requirements
- Browse & Search: Customers discover restaurants by location, cuisine, rating, price range, delivery time, and promotions.
- Menu Management: Restaurants create and update menus with items, modifiers, pricing, availability, and preparation times.
- Cart & Checkout: Customers add items, apply promo codes, see itemized pricing (subtotal, tax, delivery fee, service fee, tip), and pay.
- Order Tracking: Customers see real-time order status (placed → confirmed → preparing → ready → picked up → delivered) and driver location on a map.
- Driver Matching & Routing: The system assigns available drivers to ready orders and optimizes multi-stop routes.
- Payment Processing: Handle payments from customers and split payouts to restaurants and drivers minus platform commission.
- Notifications: Push notifications and SMS for order status changes, driver arrival, and promotions.
- Ratings & Reviews: Two-sided rating: customer rates restaurant and driver; restaurant can rate customer behavior.
- Customer Support: In-app support for missing items, wrong orders, refunds, and complaints.
- Analytics: Restaurant portal with sales, menu performance, and customer insights. Driver dashboard with earnings and efficiency metrics.
Non-Functional Requirements
| Requirement | Target | Why |
|---|---|---|
| Availability | 99.99% (52 min downtime/year) | Every minute of downtime during dinner rush loses millions |
| Order latency (P99) | < 500ms | Checkout must feel instant |
| Search latency (P95) | < 200ms | Users expect instant search results |
| Location update throughput | 50K GPS events/sec | 2M drivers × 5 updates/min ÷ 60 |
| ETA accuracy | ±3 minutes (P80) | Under-promising and over-delivering builds trust |
| Data durability | 99.999999999% (11 nines) | Payment and order data cannot be lost |
| Consistency model | Strong for payments, eventual for tracking | Financial correctness vs. real-time freshness |
| Geo query latency | < 50ms | Nearby 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.
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
| Factor | Weight | Description |
|---|---|---|
| Relevance score | 35% | Full-text match quality (name, cuisine, dish names) |
| Proximity | 25% | Distance from user's delivery address |
| Rating | 15% | Average rating and number of reviews |
| Estimated delivery time | 15% | Lower is better — factoring in prep + delivery |
| Popularity | 10% | Order volume in the last 7 days |
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
};
}
}
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
| Component | Calculation | Example ($25 order) |
|---|---|---|
| Subtotal | Sum of item prices × quantities | $22.00 |
| Modifier charges | Add-ons (extra cheese, avocado, etc.) | $3.00 |
| Tax | Local tax rate × taxable amount | $1.98 |
| Delivery fee | Distance-based + driver supply factor | $3.99 |
| Service fee | Platform commission (10-15%) | $2.50 |
| Small order fee | If subtotal < $10 threshold | $0.00 |
| Promo discount | Coupon code applied | -$5.00 |
| Tip | User-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)
}
};
}
}
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)
});
}
}
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 Factor | Constraint |
|---|---|
| Maximum orders per batch | 3 (varies by market) |
| Maximum detour time | 8 minutes additional per order |
| Restaurant overlap | Must be within 500m of each other |
| Delivery zone overlap | Orders must be heading in similar direction |
| Temperature compatibility | Hot and cold items can't be batched beyond 15 min |
| Customer preference | Premium 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
});
}
}
}
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
| Component | Source | Weight |
|---|---|---|
| Kitchen prep time | Restaurant's historical average + current backlog | 35% |
| Driver-to-restaurant time | Google Maps ETA from driver to restaurant | 20% |
| Pickup wait time | Historical pickup times for this restaurant | 10% |
| Restaurant-to-customer time | Google Maps ETA with real-time traffic | 30% |
| Batching overhead | Additional stops in multi-order delivery | 5% |
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);
}
}
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 Ratio | Surge Multiplier | Effect |
|---|---|---|
| < 0.8 | 1.0x (no surge) | Normal delivery fee |
| 0.8 - 1.2 | 1.0x | Balanced market |
| 1.2 - 1.8 | 1.2x - 1.5x | Moderate surge, attracts more drivers |
| 1.8 - 3.0 | 1.5x - 2.0x | High surge, significant driver incentive |
| > 3.0 | 2.0x - 2.5x (capped) | Maximum surge, premium pricing shown to customer |
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.
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
| Event | Recipient | Channel | Template |
|---|---|---|---|
| Order placed | Customer | Push + Email | "Your order from {restaurant} has been placed!" |
| Order confirmed | Customer | Push | "{restaurant} confirmed your order. Preparing now." |
| Order ready | Driver | Push + SMS | "Order #{id} ready for pickup at {restaurant}" |
| Driver assigned | Customer | Push | "{driver} is picking up your order. ETA: {time}" |
| Driver arriving | Customer | Push + SMS | "{driver} is almost there! 2 minutes away." |
| Order delivered | Customer | Push | "Your order has been delivered. Enjoy!" |
| Promotion | Customer | Push | "{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."
};
}
}
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 Area | How Ratings Affect It |
|---|---|
| Search ranking | Restaurants below 4.0 stars are demoted; above 4.5 get a boost |
| Driver matching | Drivers below 4.2 stars receive fewer premium order offers |
| Restaurant visibility | Restaurants below 3.8 stars may be hidden from search results |
| Driver deactivation | Drivers below 4.0 stars after 50+ deliveries are reviewed for deactivation |
| Customer trust scores | Customers 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
| Type | Description | Example |
|---|---|---|
| Percentage discount | Off entire order or specific items | "20% off your first order, max $15" |
| Free delivery | Waive delivery fee | "Free delivery on orders over $25" |
| Fixed amount off | Dollar amount deducted from total | "$5 off orders of $30+" |
| BOGO | Buy one get one free | "Buy 1 entree, get 1 free" |
| Loyalty points | Earn points per dollar spent | "Earn 1 point per $1 spent" |
| Referral bonus | Credit for referring new users | "Give $10, get $10" |
| Subscription discount | Monthly 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
| Tier | Points Required | Benefits |
|---|---|---|
| Bronze | 0 - 499 | Earn 1 pt/$1, birthday reward |
| Silver | 500 - 1,999 | Earn 1.5 pts/$1, free delivery 1x/month, priority support |
| Gold | 2,000 - 4,999 | Earn 2 pts/$1, free delivery 2x/month, exclusive deals |
| Platinum | 5,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
| Metric | Description | Time Granularity |
|---|---|---|
| Revenue | Total sales, net of refunds and commissions | Hourly, daily, weekly |
| Order volume | Number of orders, average order value | Hourly (peak analysis) |
| Preparation time | Average time from order accepted to ready | Daily (trend) |
| Menu performance | Top items, items with low conversion, modifier attach rate | Weekly |
| Rating trends | Average rating over time, common complaints | Weekly |
| Peak hours | Heatmap of order volume by hour and day | Weekly |
| Customer retention | Repeat order rate, customer lifetime value | Monthly |
Driver Analytics
| Metric | Description |
|---|---|
| Hourly earnings | Base pay + tips + bonuses per active hour |
| Deliveries per hour | Efficiency metric (target: 2-3 per hour) |
| Acceptance rate | % of order offers accepted (affects matching score) |
| On-time rate | % of deliveries within estimated time |
| Customer ratings | Average rating and recent trend |
| Distance driven | Total 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
| Dashboard | Key Metrics | Alert Threshold |
|---|---|---|
| Order Pipeline | Orders/minute, failure rate, avg processing time | Failure rate > 0.1% |
| Driver Tracking | GPS updates/sec, WebSocket connections, drop rate | Drop rate > 2% |
| Payment Processing | Authorization success rate, capture failures, refund rate | Auth failures > 1% |
| Search Performance | Query latency P95, index freshness, result relevance | P95 > 300ms |
| KDS Connectivity | Active connections, message delivery rate | Delivery rate < 99.9% |
| ETA Accuracy | Prediction vs actual, per-zone accuracy | P80 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;
}
}
}
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
| SLA | Target | Measurement |
|---|---|---|
| Order placement success rate | 99.99% | Successful orders / attempted orders per minute |
| Search availability | 99.99% | Search queries returning results / total queries |
| Driver GPS availability | 99.95% | Active WebSocket connections / total active drivers |
| Payment success rate | 99.9% | Successful authorizations / total attempts |
| ETA accuracy | ±3 min (P80) | Prediction vs actual delivery time per order |
| Notification delivery rate | 99.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
| Threat | Attack Vector | Impact | Mitigation |
|---|---|---|---|
| Payment fraud | Stolen credit cards | Chargebacks, financial loss | 3D Secure, velocity checks, device fingerprinting |
| Refund abuse | Multiple accounts claiming missing items | Revenue loss | Refund rate tracking, account linkage detection |
| Promo abuse | Creating accounts for new customer promos | Promo budget drain | Device fingerprint, phone number verification |
| Driver fraud | Marking orders delivered without pickup | Driver pay fraud | GPS verification, photo proof of delivery |
| Data breach | Address/payment data exposure | Regulatory fines, trust loss | Encryption at rest, PCI DSS compliance, tokenization |
| Account takeover | Credential stuffing | Unauthorized orders | MFA, 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
| Area | Regulation | Platform Responsibility |
|---|---|---|
| Food Safety | Local health department regulations | Verify restaurants have valid permits, track food handling compliance |
| Data Privacy | GDPR, CCPA | Data minimization, right to deletion, consent management for location tracking |
| Gig Worker | AB5 (California), EU Platform Workers Directive | Classify drivers correctly, provide benefits where required |
| Financial | PCI DSS, PSD2 | Secure payment processing, SCA for EU transactions |
| Tax | 1099 reporting (US), VAT (EU) | Report driver earnings, collect and remit applicable taxes |
| Accessibility | ADA, WCAG 2.1 | Accessible app design for customers with disabilities |
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
| Service | Specification | Monthly 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 Kafka | 6 brokers, 3TB storage | $5,500 |
| ClickHouse (Analytics) | 3-node cluster, 6TB data | $4,800 |
| WebSocket Gateway | 10 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 |
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
| Method | Endpoint | Description |
|---|---|---|
GET | /api/v1/restaurants?lat={lat}&lng={lng} | Search nearby restaurants |
GET | /api/v1/restaurants/{id}/menu | Get restaurant menu |
POST | /api/v1/orders | Place an order |
GET | /api/v1/orders/{id} | Get order status and details |
PATCH | /api/v1/orders/{id}/cancel | Cancel an order |
GET | /api/v1/orders/{id}/track | Get real-time tracking (WebSocket upgrade) |
POST | /api/v1/cart | Create/update cart |
POST | /api/v1/promos/validate | Validate a promo code |
POST | /api/v1/support/tickets | Create support ticket |
POST | /api/v1/ratings | Submit 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 Type | Scope | Target Coverage | Tools |
|---|---|---|---|
| Unit tests | Pricing engine, state machine, validators | 90%+ | xUnit, Moq |
| Integration tests | Service + database interactions | 80%+ | Testcontainers, WebApplicationFactory |
| Contract tests | API request/response schemas | All public endpoints | Pact |
| E2E tests | Full order lifecycle | Critical paths | Playwright, custom fixtures |
| Load tests | Peak traffic simulation | 15K QPS sustained | k6, Gatling |
| Chaos tests | Failure scenarios | All critical paths | Litmus, 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
| Scenario | Target | Success Criteria |
|---|---|---|
| Friday dinner rush | 15K orders/sec | P99 latency < 500ms, 0% errors |
| Search during peak | 50K queries/sec | P95 latency < 200ms |
| GPS ingestion surge | 50K events/sec | All events processed within 2 seconds |
| Payment burst | 8K authorizations/sec | P99 latency < 2 seconds |
| Database failover | Primary down | Failover in < 30s, zero order loss |
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
| Metric | Value |
|---|---|
| Order placement latency (P99) | < 500ms |
| Search latency (P95) | < 200ms |
| GPS update frequency | Every 10 seconds |
| Driver matching time | < 30 seconds from order ready |
| ETA accuracy (P80) | ±3 minutes |
| Restaurant confirmation timeout | 15 minutes |
| Average delivery time | 25-35 minutes |
| Maximum batch size | 3 orders per driver trip |
| Payment hold duration | 48 hours (authorization window) |
| Refund auto-resolution threshold | $20 per incident |
| Platform commission rate | 25-30% of subtotal |
| Search ranking refresh interval | 30 seconds |
| Menu cache TTL | 30 seconds |
| Peak QPS capacity | 15,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)