Food delivery system
Low-level design for a food-delivery platform: restaurant menu management, cart and order processing, delivery assignment, order state machine, pricing with promotions, and real-time tracking notifications.
The Problem
Your company operates a food delivery platform serving 200 restaurants across a metro area. Right now, order processing lives in a 3,000-line OrderController class. When a restaurant marks an order as "ready for pickup," the system assigns a delivery agent by looping through every agent in the database, picking the first one whose status column says "available." Last Friday evening, a popular Thai restaurant received 40 orders in 10 minutes. The assignment loop kept selecting the same agent (alphabetically first), that agent got overloaded, and 38 customers waited over an hour for cold food.
Food delivery systems are harder than standard e-commerce because the order has a rich lifecycle. An order does not just go from "placed" to "delivered." It flows through restaurant acceptance, preparation, pickup, and transit, with each transition triggering notifications to different actors. Delivery assignment must consider agent location, current load, and restaurant proximity. Pricing involves base item costs, customizations, delivery fees, and promotions that stack or conflict.
Design the core classes for a food delivery platform that handles restaurant menu management, cart validation, order placement, order state machine transitions, delivery agent assignment, pricing with promotional discounts, and real-time order tracking notifications.
Requirements
Clarifying Questions
Before jumping into class design, ask questions to narrow the problem. Cover four areas: core actions, error handling, boundaries, and future extensions.
You: "Can a customer add items from multiple restaurants into a single cart, or is the cart locked to one restaurant at a time?"
Interviewer: "One restaurant per cart. If the customer wants to order from a different restaurant, the current cart must be cleared first."
Single-restaurant cart simplifies delivery logistics. One order maps to one restaurant and one delivery agent. No need for split-order routing.
You: "How are delivery agents assigned to orders? Does the restaurant pick one, or does the system handle it automatically?"
Interviewer: "The system assigns automatically when the restaurant accepts the order. The agent must be available and within a reasonable radius of the restaurant."
Automatic assignment means the system needs a strategy for choosing among available agents. Nearest agent is the obvious baseline, but load balancing matters during peak hours.
You: "Is there a maximum number of orders a delivery agent can handle at the same time?"
Interviewer: "Yes, each agent has a configurable maximum concurrent deliveries. Usually two, but senior agents can handle three."
Agent capacity is a constraint in the assignment algorithm. You cannot assign an order to an agent who is already at capacity, even if that agent is the closest.
You: "What promotions does the system support? Can customers stack multiple promotions on a single order?"
Interviewer: "Support flat discount, percentage off, free delivery, and buy-one-get-one. No stacking for the initial design. The system applies the single best applicable promotion automatically."
No stacking means the pricing engine evaluates all eligible promotions and picks the one that gives the customer the largest discount. That is a Strategy pattern with a comparison step.
You: "Can a customer cancel an order at any point, or is there a cutoff?"
Interviewer: "Cancellation is allowed only before the restaurant starts preparing. Once the order moves to PREPARING, cancellation is not allowed. Provide a full refund on valid cancellations."
Cancellation is a state machine constraint. The transition to CANCELLED is only valid from PLACED or ACCEPTED states.
You: "Does the system need to handle surge pricing or delivery radius limits?"
Interviewer: "Not for the initial design. Assume a fixed delivery fee based on distance. Surge pricing and radius limits are good extensibility topics."
Good. Fixed delivery fee keeps the pricing model clean. Surge pricing can be added as a new strategy later.
You: "What notifications does the system send, and to whom?"
Interviewer: "Notify the customer on every state change. Notify the restaurant when a new order is placed. Notify the delivery agent when they are assigned an order. Provide real-time ETA updates during transit."
Four notification events: order placed (restaurant), agent assigned (agent), every state change (customer), and ETA updates (customer). That is an Observer pattern with multiple subscriber types.
You: "Does the system track delivery agent location in real time?"
Interviewer: "Yes, agents report their GPS coordinates periodically. The system uses the latest coordinates for assignment decisions and customer-facing ETA."
Real-time location means the agent entity has a mutable location field that updates frequently. Assignment algorithms use the latest snapshot.
Perfect. You have clarified scope and ruled out unnecessary complexity. The core system is a single-restaurant cart, automatic agent assignment with capacity limits, a strict order state machine, best-promotion-wins pricing, and observer-based notifications.
Final Requirements
Functional Requirements:
- Restaurants register with a menu of categorized items, each with a price, description, and availability flag
- Customers add items from a single restaurant to a cart with quantity and customization options
- The system processes orders: validates cart, calculates pricing with the best applicable promotion, captures payment, and creates the order
- Orders follow a strict state machine: PLACED, ACCEPTED, PREPARING, READY_FOR_PICKUP, PICKED_UP, DELIVERED, CANCELLED
- The system assigns the best available delivery agent when the restaurant accepts the order
- Customers, restaurants, and delivery agents receive real-time notifications on relevant state changes
Non-Functional Requirements:
- Thread safety for concurrent order placement and agent assignment
- Extensibility for new promotion types, delivery assignment strategies, and order states
- Clean separation between pricing logic, assignment logic, and notification dispatch
Out of Scope:
- UI rendering and REST API layer
- Database persistence and ORM mapping
- Surge pricing and dynamic delivery radius
- Multi-restaurant carts and order splitting
- Scheduled or pre-orders
- Payment gateway integration (assume a simple
PaymentServiceinterface)
Example Inputs and Outputs
Scenario 1: Successful order with promotion
- Customer adds 2x Pad Thai ($14 each) and 1x Spring Rolls ($8) from "Thai Garden"
- A 10% off promotion ("WELCOME10") is active
- Cart subtotal: $36.00. Discount: $3.60. Delivery fee: $5.00. Total: $37.40
- Order is placed, restaurant accepts, agent is assigned, food is prepared, picked up, and delivered
- Customer receives 6 notifications (one per state change)
Scenario 2: Cart from wrong restaurant
- Customer has a cart with items from "Thai Garden"
- Customer tries to add "Margherita Pizza" from "Pizza Palace"
- System rejects: "Cart contains items from another restaurant. Clear cart first."
Scenario 3: Cancellation after preparation starts
- Customer places order, restaurant accepts, then moves order to PREPARING
- Customer requests cancellation
- System rejects: "Order cannot be cancelled after preparation has started."
- Order continues through the normal lifecycle
Scenario 4: No available delivery agent
- Restaurant accepts an order during peak hours
- All agents within radius are at maximum capacity
- System queues the assignment and retries when an agent becomes available
- Order stays in ACCEPTED until an agent is found
Try It Yourself
Try it yourself
Before reading the solution, spend 20 minutes sketching your class diagram. Focus on the order state machine first, since every other feature depends on it. Think about which transitions are valid, who triggers each transition, and what side effects (notifications, agent assignment) each transition produces. Compare your approach with the walkthrough below.
Step 1: Identify Core Entities
Start by asking: what are the main "things" in this problem? Scan the requirements for nouns that carry state or behavior. A restaurant has a menu. A menu has items. A customer builds a cart. The cart becomes an order. An agent delivers the order. A promotion adjusts pricing.
A common mistake is cramming everything into an Order god-class. Good design means each class has a single, clear job. The order tracks lifecycle state. The cart handles item collection and validation. Pricing is a separate concern.
| Entity | Responsibility | Key Attributes |
|---|---|---|
| Restaurant | Owns a menu and operating status. Source of items. | id, name, address, location, isOpen |
| MenuItem | A single orderable item with price and availability. | id, name, price, available, restaurantId |
| Cart | Collects items from one restaurant before checkout. | customerId, restaurantId, items |
| CartItem | Wraps a MenuItem with quantity and customizations. | menuItem, quantity, customizations |
| Order | The core lifecycle entity. Tracks state from placement to delivery. | id, customer, restaurant, items, status, pricing |
| OrderItem | Snapshot of a cart item at the moment of order creation. | menuItemName, unitPrice, quantity, lineTotal |
| Customer | Places orders and receives notifications. | id, name, address, phone |
| DeliveryAgent | Picks up and delivers orders. Has location and capacity. | id, name, location, activeOrders, maxCapacity |
| Address | Value object for delivery and restaurant addresses. | street, city, latitude, longitude |
| Payment | Records payment capture for an order. | id, orderId, amount, status |
| Promotion | Defines a discount rule: flat, percentage, free delivery, or BOGO. | id, code, type, value, active |
| OrderStatus | Enum representing the order lifecycle states. | PLACED, ACCEPTED, PREPARING, READY_FOR_PICKUP, PICKED_UP, DELIVERED, CANCELLED |
Notice that Cart and Order are separate entities. The cart is a transient workspace that the customer modifies freely. The order is an immutable record created at checkout. Merging them would let post-checkout modifications leak into active orders.
Step 2: Define Relationships and Class Design
Class Diagram
Deriving the Order Class
Order is the central entity. Every other component feeds into it or reacts to its state changes.
Deriving state from requirements:
| Requirement | What Order must track |
|---|---|
| "Validates cart and creates order" | customer, restaurant, items (snapshot), pricing breakdown |
| "Strict state machine: PLACED through DELIVERED" | current status, status history with timestamps |
| "Assigns delivery agent on acceptance" | assigned agent (nullable until assigned) |
| "Calculates pricing with best promotion" | subtotal, discount, delivery fee, total |
| "Cancellation allowed before PREPARING" | status must be queryable for transition validation |
This gives us the state:
Order:
id, customer, restaurant, items[]
status (enum), statusHistory[]
agent (nullable)
pricing: { subtotal, discount, deliveryFee, total }
placedAt, deliveredAt (nullable)
Deriving methods from needs:
| Need from requirements | Method |
|---|---|
| "State machine transitions" | transitionTo(newStatus) |
| "Assign agent on acceptance" | assignAgent(agent) |
| "Query current state" | getStatus(), canTransitionTo(status) |
| "Calculate total" | getTotal() |
We keep Order focused on state and data. The OrderService orchestrates transitions, triggers notifications, and enforces business rules. Order itself does not call external services.
Deriving the Cart Class
The cart is the customer's workspace before checkout. It enforces the single-restaurant rule and validates item availability.
Deriving state from requirements:
| Requirement | What Cart must track |
|---|---|
| "Items from a single restaurant" | restaurantId (locked on first item) |
| "Quantity and customizations" | list of CartItems with quantity |
| "Clear cart to switch restaurants" | must support clearing all items |
Deriving methods from needs:
| Need from requirements | Method |
|---|---|
| "Add item from same restaurant" | addItem(menuItem, quantity, customizations) |
| "Remove item" | removeItem(menuItemId) |
| "Switch restaurant" | clear() |
| "Calculate subtotal for pricing" | getSubtotal() |
The addItem method checks whether the cart is empty (any restaurant allowed) or locked to a specific restaurant (reject items from other restaurants). This is the single-restaurant enforcement point.
Deriving the DeliveryAgent Class
The agent is the mobile actor. Assignment algorithms need to query location and capacity.
| Requirement | What DeliveryAgent must track |
|---|---|
| "Available and within radius" | current latitude/longitude |
| "Maximum concurrent deliveries" | activeOrders list, maxCapacity |
| "Configurable capacity" | maxCapacity field (2 or 3) |
Continue Reading with Premium
Unlock this article and every other in-depth system design guide on the platform with NotesFromSDE Premium.