Design a Food Delivery System

Open-ended practice prompt: restaurants, orders, delivery agents, the three-sided state machine in a different shell.

Exercise Intermediate
12 min read
exercise ood food-delivery state-machine

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 PromoCode applied 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 an Order.)
  • 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:

  • Order HAS-A Customer, HAS-A Restaurant, HAS-A DeliveryAgent (nullable until assigned), HAS-A list of OrderItem, HAS-A Address, HAS-A Payment.
  • Restaurant HAS-A Menu HAS-A list of MenuItem.
  • DispatchService USES Order to assign an agent — assigned via a method on Order, not by mutating the order from outside.
  • Order is the aggregate root for the entire post-checkout lifecycle. All transitions go through methods on Order.

Two things to avoid:

  • A Restaurant subclass per cuisine or per category. Use composition: Restaurant has tags or categories as fields.
  • Separate PlacedOrder / PreparingOrder / DeliveringOrder classes. State pattern done wrong — the state is on Order as an enum (or a State object held by Order), 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:

DrawSkip
The three actor classes and OrderCuisine / category taxonomy details
Menu / MenuItem with prices, but not authoringImage and description fields beyond the name
DispatchService as a collaborator with one methodA full Map / GeoIndex class
Payment as a value with status, but not the gatewayReceipt PDF formatting
The OrderState, RestaurantState, AgentState, CustomerState enumsEvery 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.

  1. Customer.addToCart(menuItem, qty) — Customer goes Browsing → CartHasItems.
  2. Customer.placeOrder(address, paymentMethod) — builds an Order from the cart, charges (or authorises) the payment, hands the order to Restaurant.acceptOrder(). Order goes → Placed; Customer goes CartHasItems → Waiting.
  3. Restaurant.acceptOrder(order) — Restaurant transitions through ReceivingOrders → Preparing for this specific order. Order goes Placed → Preparing. At this point (using the policy we picked in Phase 1) DispatchService.assign(order) is invoked.
  4. DispatchService.assign() returns a DeliveryAgent. Order.assignAgent(agent) — Order’s agent field is set; Agent goes Available → Assigned. Note that this is happening in parallel with cooking.
  5. Restaurant finishes cooking. Restaurant.markReady(order) — Order goes Preparing → ReadyForPickup.
  6. Agent arrives, picks up the food. Order.pickUp() — Order goes ReadyForPickup → InTransit; Agent goes Assigned → Delivering.
  7. Agent reaches the customer. Order.deliver() — Order goes InTransit → Delivered; Agent goes Delivering → Available (after a brief Returning if you model it); Customer goes Waiting → Delivered.

Then narrate one unhappy path. Cancellation by the customer after placement but before the restaurant starts cooking:

  • Customer.cancelOrder()Order.cancel(by=CUSTOMER).
  • Order consults a RefundPolicy — if state is Placed, full refund; if Preparing, partial; if past ReadyForPickup, refund is a support decision.
  • Order goes Placed → Cancelled; Restaurant is notified and falls back to its prior state; Customer goes Waiting → 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.

DecisionRecommended answerThe 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. Preparing is the standard industry choice — agent arrives roughly as the food is ready. If we had higher rejection rates I’d shift to Ready; if average prep time was tiny I’d shift to Place.”
  • Menu items as a separate class vs strings on order.MenuItem is its own class because it has price, modifiers, and availability — all of which can change between two orders. The OrderItem snapshots 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 Order knows about every consumer; with Observer it does not. The cost is the indirection; the benefit is that adding push notifications next quarter does not touch Order.”
  • In-memory OrderRepository vs 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 put Order in a transactional store keyed by order id, with Agent.location in a separate geospatial store.”
  • Refund policy as a method on Order vs a policy class. “I put it in RefundPolicy because the rules differ by reason (customer cancel / restaurant reject / agent failure), by state, and by region — and they change frequently. Order.cancel() and Order.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.

AxisStrong signalWeak signal
ScopingNames the three actors and the four state machines in the first five minutes; commits to an assignment timing.Starts modelling menu items before clarifying.
DecompositionOrder 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 fluencyDraws 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 awarenessCommits 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.
CommunicationNarrates 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.

  • 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 RefundPolicy and DispatchService.
Search ESC

Keyboard shortcuts

Shortcuts are disabled while typing in inputs.