Design a Food Delivery System
Open-ended practice prompt: restaurants, orders, delivery agents, the three-sided state machine in a different shell.
Scenario#
You will be given a prompt of the form “design a food delivery system” — sometimes named after a concrete product (DoorDash, Uber Eats, Zomato, Swiggy), sometimes generic. The interviewer wants to see whether you can model three actors plus a queue between two of them: the customer, the restaurant, and the delivery agent — and the order itself, which moves through a longer state machine than a ride because the food has to be cooked between matching and delivery. The single hardest thing in this prompt is keeping the four state machines coherent when the restaurant’s progress and the agent’s progress run in parallel.
What is actually being tested:
- Can you name the four state machines (customer, restaurant, agent, order) and their transitions without being prompted?
- Can you handle the parallelism between cooking and dispatch — does the agent get assigned before the food is ready, or after?
- Can you scope out menu authoring, inventory, and promotions early without losing them?
- Can you handle a refund / cancellation post-pickup as a first-class flow, not a footnote?
This writeup is a structured walk-through of the 45-minute round for this specific prompt — entities, the four-way state machine, the strategies that show up, and the trade-offs the interviewer is most likely to push on.
Constraints#
The constraints are about the room, not the problem:
- 45 minutes. Roughly 5 for introductions, 30–35 working, 5 for the interviewer’s questions. The temptation in this prompt is to spend 20 minutes on menu modelling; resist.
- Shared editor or whiteboard. Class diagram + the four-state-machine sketch are the takeaway artefacts.
- One language, usually Java. Code sketches stay tiny.
- In-process design. Domain model only. You can call out service boundaries when asked but you do not draw infrastructure unless prompted.
- Out of scope by default: menu authoring UI, inventory management, promotion engine internals, image hosting, restaurant onboarding, agent payouts, tip calculation, regulatory food-safety logging, fraud detection, multi-cuisine recommendation. Name them out loud and move on.
The prompts vary; the structure does not.
Approach#
The 45-minute round breaks into the same five phases as any LLD round — only the entities differ. The minute targets below are the median.
Phase 1 — Clarify scope (5 minutes)#
The five questions worth asking, in roughly decreasing leverage:
- Who are the users? Customer, Restaurant (an operator using a tablet), and DeliveryAgent. Is there an admin who suspends accounts? A support agent who can refund orders? Naming this fixes auth and the use case diagram.
- One restaurant per order, or multi-restaurant carts? Multi-restaurant is a different beast — one order forks into N sub-orders with their own state machines. Default to single-restaurant unless they push.
- When does the agent get assigned — at order placement, at “preparing”, or at “ready”? This is the single biggest design call in the prompt. All three are defensible; you must pick one and own it.
- Pricing and promotions: in or out of scope? Promotions are usually cut to a sentence (“a
PromoCodeapplied at checkout reduces the line-item total — we will not design the promo engine”). Tip is usually a number on the order. - Refund / cancellation policy: who can cancel and at what stages? Customer can cancel before “preparing”; after that it is a refund decision. Restaurant can reject. Agent can mark undelivered. This shapes the state machine.
End by reading the scope back: “So we are designing single-restaurant orders for actors A / B / C, agent assigned at Preparing, promo as a flat discount, with bilateral cancellation up to Preparing and refund after — ignoring inventory, menu authoring, and recommendations. Right?”
Phase 2 — Identify entities and relationships (5 minutes)#
The nouns for this prompt cluster into four groups:
- Actors and accounts:
Customer,Restaurant,DeliveryAgent,User(optional shared base). - The catalogue:
Menu,MenuItem,Cart,CartItem. (Cart is a transient pre-order container; once placed it becomes anOrder.) - The order itself:
Order,OrderItem,Address,Payment. - Policy and platform:
DispatchService,PricingService,PromoCode,RefundPolicy,OrderRepository.
That is 12–14 nouns. A touch high; collapse OrderItem and CartItem into a single value object if you want fewer boxes, or keep them separate to show that they live different lifecycles.
Relationships worth calling out:
OrderHAS-ACustomer, HAS-ARestaurant, HAS-ADeliveryAgent(nullable until assigned), HAS-A list ofOrderItem, HAS-AAddress, HAS-APayment.RestaurantHAS-AMenuHAS-A list ofMenuItem.DispatchServiceUSESOrderto assign an agent — assigned via a method onOrder, not by mutating the order from outside.Orderis the aggregate root for the entire post-checkout lifecycle. All transitions go through methods onOrder.
Two things to avoid:
- A
Restaurantsubclass per cuisine or per category. Use composition:Restauranthas tags or categories as fields. - Separate
PlacedOrder/PreparingOrder/DeliveringOrderclasses. State pattern done wrong — the state is onOrderas an enum (or a State object held byOrder), not as a class hierarchy.
Phase 3 — Draw the class diagram (10–12 minutes)#
The core boxes and their important methods:
+---------------+ +---------------+ +---------------+| Customer | | Order | | Restaurant |+---------------+ +---------------+ +---------------+| - id | | - id | | - id || - addresses | 1 * | - customer | * 1 | - menu || - cart |------| - restaurant |------| - state || - state | | - agent | +---------------++---------------+ | - items | | acceptOrder() || addToCart() | | - state | | startCooking()|| placeOrder() | | - payment | | markReady() || cancelOrder() | | - total | +---------------++---------------+ +---------------+ | place() | ^ | accept() | | uses | startCooking()| | | markReady() | +---------------+ | assignAgent() |----->| DispatchSvc | | pickUp() | +---------------+ | deliver() | | assign() | | cancel() | +---------------+ | refund() | +---------------+ +---------------+ | | DeliveryAgent | | +---------------+ | 1 * | - id | +-------------- | - location | | - state | +---------------+ | accept() | | pickUp() | | deliver() | | goOffline() | +---------------+Things to draw and to not draw:
| Draw | Skip |
|---|---|
The three actor classes and Order | Cuisine / category taxonomy details |
Menu / MenuItem with prices, but not authoring | Image and description fields beyond the name |
DispatchService as a collaborator with one method | A full Map / GeoIndex class |
Payment as a value with status, but not the gateway | Receipt PDF formatting |
The OrderState, RestaurantState, AgentState, CustomerState enums | Every getter / setter |
Phase 4 — Narrate a flow (8–10 minutes)#
Walk through the happy path as a sequence of object messages. The four-state-machine synchronisation is the moment most candidates lose the thread — narrate the transitions out loud.
Customer.addToCart(menuItem, qty)— Customer goesBrowsing → CartHasItems.Customer.placeOrder(address, paymentMethod)— builds anOrderfrom the cart, charges (or authorises) the payment, hands the order toRestaurant.acceptOrder(). Order goes→ Placed; Customer goesCartHasItems → Waiting.Restaurant.acceptOrder(order)— Restaurant transitions throughReceivingOrders → Preparingfor this specific order. Order goesPlaced → Preparing. At this point (using the policy we picked in Phase 1)DispatchService.assign(order)is invoked.DispatchService.assign()returns aDeliveryAgent.Order.assignAgent(agent)— Order’s agent field is set; Agent goesAvailable → Assigned. Note that this is happening in parallel with cooking.- Restaurant finishes cooking.
Restaurant.markReady(order)— Order goesPreparing → ReadyForPickup. - Agent arrives, picks up the food.
Order.pickUp()— Order goesReadyForPickup → InTransit; Agent goesAssigned → Delivering. - Agent reaches the customer.
Order.deliver()— Order goesInTransit → Delivered; Agent goesDelivering → Available(after a briefReturningif you model it); Customer goesWaiting → Delivered.
Then narrate one unhappy path. Cancellation by the customer after placement but before the restaurant starts cooking:
Customer.cancelOrder()→Order.cancel(by=CUSTOMER).Orderconsults aRefundPolicy— if state isPlaced, full refund; ifPreparing, partial; if pastReadyForPickup, refund is a support decision.- Order goes
Placed → Cancelled; Restaurant is notified and falls back to its prior state; Customer goesWaiting → Idle; payment is reversed.
A second unhappy path worth mentioning: agent unable to deliver (customer not home). Order.deliveryFailed() — Order goes InTransit → DeliveryFailed; the policy decides whether to retry, refund, or return-to-restaurant. Bring this up only if time permits.
Phase 5 — Defend trade-offs (5–8 minutes)#
The trade-offs section below is the menu; the interviewer will pick two or three.
Design decisions to make#
Four decisions you must explicitly make on the board. Pre-rehearsing them is the highest-leverage prep for this prompt.
| Decision | Recommended answer | The trap |
|---|---|---|
| When does the agent get assigned? | At Preparing. Earliness reduces customer wait; lateness reduces the risk of an idle agent. Preparing is the usual industry compromise. | Not picking one and waving at “it depends”. |
| Where does the state machine live? | On Order as the aggregate root. The four actor enums transition only in response to events from Order. | Four independent state machines mutating each other directly. |
| Menu, inventory, and promo as in-scope? | Menu in (read-only), inventory out, promo as a flat discount on the order (no rules engine). | Trying to model the promo engine; you will never finish. |
| How do the four states stay in sync? | Observer pattern — Order is the subject, the three actors are observers. One transition fires N notifications. | Mutating customer.state and agent.state directly from Order.deliver() — couples four classes pairwise. |
If you can name your answer to each of these for this prompt, you have already done 60% of the design work.
Trade-offs to discuss#
The five most common follow-up trade-offs and the shape of a defensible answer.
- Assign-at-place vs assign-at-preparing vs assign-at-ready. “I picked assign-at-
Preparing. Assign-at-place wastes agent time when restaurants reject orders or take long to start; assign-at-ready maximises waste of agent time around pickup but minimises the risk of paying an agent for a cancelled order.Preparingis the standard industry choice — agent arrives roughly as the food is ready. If we had higher rejection rates I’d shift toReady; if average prep time was tiny I’d shift toPlace.” - Menu items as a separate class vs strings on order. “
MenuItemis its own class because it has price, modifiers, and availability — all of which can change between two orders. TheOrderItemsnapshots name and price at order time so a later menu edit does not retroactively rewrite history. The cost is two classes; the benefit is auditable history.” - Observer vs direct method calls between Order and the actors. “Observer because the customer notification, the restaurant tablet, the agent app, and the analytics pipeline are all independently subscribed to order events. With direct calls
Orderknows about every consumer; with Observer it does not. The cost is the indirection; the benefit is that adding push notifications next quarter does not touchOrder.” - In-memory
OrderRepositoryvs persistence. “In-memory satisfies the prompt. The repository is an interface so we can plug a database in later — the seam is in the right place. For a real system I’d putOrderin a transactional store keyed by order id, withAgent.locationin a separate geospatial store.” - Refund policy as a method on Order vs a policy class. “I put it in
RefundPolicybecause the rules differ by reason (customer cancel / restaurant reject / agent failure), by state, and by region — and they change frequently.Order.cancel()andOrder.deliveryFailed()consult the policy to compute the refund.”
Evaluation criteria#
Interviewers usually rank candidates on five rough axes. Weak signals weigh more than strong ones.
| Axis | Strong signal | Weak signal |
|---|---|---|
| Scoping | Names the three actors and the four state machines in the first five minutes; commits to an assignment timing. | Starts modelling menu items before clarifying. |
| Decomposition | Order as a clear aggregate root; menu / cart / order as distinct lifecycles; one or two well-chosen patterns (State, Observer, Strategy). | A Order god class; menu and order conflated; one class per state. |
| State-machine fluency | Draws all four state machines and the synchronisation seam; handles cancellation and refund as first-class flows; understands the parallelism between cooking and dispatch. | Only the happy path; refund as an afterthought; agent and order states left implicit. |
| Trade-off awareness | Commits to an assignment timing and a refund policy and defends them; names the breakpoint at which the alternative wins. | Lists “we could use A or B” without committing; refuses to pick. |
| Communication | Narrates as they draw; checks in after each phase; finishes the round with time for follow-ups. | Long silences during the state-machine sketch; abandons the original problem to dive into the promo engine. |
A passing round is consistent on scoping and state-machine fluency, with at least passable signal on the other three.
Related exercises#
- Approaching the OOD Interview — the meta-script. Read this first if you have not internalised the five phases.
- Design a Ride-Sharing System — the same three-sided shape with shorter state machines (no cooking step). Practising both makes the pattern stick.
- Parking Lot — the canonical worked system. Smaller, but the same aggregate-root discipline applies.
- State Pattern — the formalism behind the four state machines in this exercise.
- Observer Pattern — the seam that keeps the four state machines in sync.
- Strategy Pattern — the formalism behind the swappable
RefundPolicyandDispatchService.