Javathoughts Logo
Javathoughts
Published on
Views

Design Food Delivery Order Workflow System Design Interview Guide

Authors
  • avatar
    Name
    Javed Shaikh
    Twitter

← System Design Interview Preparation

This guide walks through Design Food Delivery / Order Workflow the way you would in a backend or Java interview. The numbers are interview estimates. They help you show your thinking. They are not a production capacity plan.


1. Problem

A food delivery app is an order state machine with three humans in the loop: customer, restaurant, and delivery partner.

Example:

  • You pick a restaurant, add a biryani to the cart, pay
  • The restaurant accepts and cooks
  • A rider is assigned
  • You watch status: preparing → picked up → arriving
  • If the restaurant is out of chicken, you get a cancel and a refund

This is closer to e-commerce checkout plus live assignment, not a simple CRUD form. Cart and catalog are the front door. The workflow after place order is what interviewers want.


2. Functional Requirements / FR

RequirementWhat it means
Browse restaurantsList nearby places, menu, hours, availability.
CartAdd/remove items, quantities, restaurant lock (one restaurant per cart).
Place orderSnapshot prices, create order, start workflow.
PaymentCharge (or pre-auth) before the restaurant cooks.
Restaurant accept / prepareDashboard: accept, reject, mark prepared.
Delivery assignmentFind a nearby available partner.
Status trackingCustomer sees a live timeline.
NotificationsPush/SMS on accept, pickup, delay.
Menu availability86 an item so nobody orders it.
Cancel / refundRules depend on how far the order has gone.

Out of scope: full rider GPS platform (point to ride booking), restaurant accounting, ads.


3. Non-Functional Requirements / NFR

RequirementWhy it matters
Idempotent place-orderDouble tap should not cook two biryanis.
Status freshnessA 10-minute silent order feels stolen.
Peak lunch/dinnerQPS is spiky.
Consistency on stockOversell of a limited thali causes cancel storms.
Money correctnessRefunds must match captures.
Audit trailWho rejected, who canceled.

Interview line: the order row is the source of truth. Everyone else reacts to events.


4. Back-of-the-Envelope Calculation

Traffic assumptions

  • 5 million daily active customers
  • 10% place an order → 500,000 orders/day
  • Browse is heavier: 20 restaurant/menu views per user
  • Read/write ratio on catalog is high; order writes are the critical path

QPS

Browse:

5,000,000 × 20 = 100,000,000 views/day
100,000,000 / 86,400 ≈ 1,160 QPS

If peak is 5× (8pm), design for around 6,000 QPS of catalog reads. Cache menus.

Orders:

500,000 / 86,400 ≈ 6 QPS average

Peak 5× ≈ 30 QPS of place-order. Still small. Each order then generates restaurant poll, assignment, and status updates. Multiply by ~20 events → ~600 QPS of workflow writes at peak. The scary part is not raw order QPS. It is fan-out, payments, and support when restaurants reject.

Storage

Order + items ~ 5 KB:

500,000 × 5 KB ≈ 2.5 GB/day

Easy. Keep events for 90 days.

Menu catalog: 100,000 restaurants × 50 items × 500 bytes ≈ 2.5 GB. Cache it.

Cache / memory

Hot menus + restaurant open/closed flags. 100,000 hot restaurants × 20 KB menu ≈ 2 GB. Redis is enough. See distributed cache.

Active order tracking: 50,000 in-flight × 2 KB ≈ 100 MB.

Server estimate

Catalog at 6,000 QPS / 2,000 ≈ 3–5 read servers plus cache.

Order service: a few Java instances, but do not share the place-order database with heavy analytics.

Assignment workers: tens of consumers at peak, not thousands.

These are interview estimates, not exact production numbers.


5. APIs

Browse

GET /api/restaurants?lat=&lng=

GET /api/restaurants/:id/menu

Cart

PUT /api/cart

{
  "restaurantId": "r_12",
  "items": [{ "itemId": "i_9", "qty": 2 }]
}

Reject mixing two restaurants.

Place order

POST /api/orders

Header: Idempotency-Key: uuid

{ "cartId": "c_1", "paymentMethodId": "pm_4" }

Response: orderId, status: paid_pending_accept (or similar).

Restaurant

POST /api/merchant/orders/:orderId/accept

POST /api/merchant/orders/:orderId/reject

POST /api/merchant/orders/:orderId/ready

Delivery partner

POST /api/delivery/assignments/:id/accept

Customer tracking

GET /api/orders/:orderId

WebSocket optional for live status.

Cancel

POST /api/orders/:orderId/cancel

Errors: 409 restaurant closed, item 86'd, already accepted; 402 payment; 429.


6. Data Model

restaurants / menu_items

Hours, location, available flag per item. Inventory can be a boolean or a count for limited dishes.

carts

cart_id, user_id, restaurant_id, items JSON, updated_at.

orders

FieldNotes
order_id
user_id
restaurant_id
statusSee workflow
item_snapshotFrozen prices and names
total_cents
payment_id
delivery_partner_idNull until assigned
idempotency_keyUnique
version

order_events

Append-only status history for the timeline UI and support.

assignments

Offers to riders: pending, accepted, expired. Same race pattern as ride matching.

Do not mutate menu prices on an in-flight order. The snapshot is the contract.


7. High-Level Design

Order service is the hub. Everything else is a call or an event.

Food Delivery order workflow architecture

Food Delivery order workflow architectureThe order service talks to restaurant, payment, and the order database. An assignment worker finds a delivery partner. Status updates go out as notifications.menu / acceptorder eventstatus👤Customer🌐API Gateway⚙️Order Service🍽️Restaurant Service🗄️Order DB💳Payment Service📩Queue👷Assignment Worker🚗Delivery Partner🔔Notification Service
The order service talks to restaurant, payment, and the order database. An assignment worker finds a delivery partner. Status updates go out as notifications.

Components:

  • Customer → API Gateway → Order Service
  • Restaurant Service: menu, hours, accept/reject on a dashboard
  • Payment Service: authorize/capture/refund
  • Order DB: source of truth
  • Queue: OrderPaid, RestaurantAccepted, ReadyForPickup
  • Assignment Worker: finds a delivery partner
  • Notification Service: customer + restaurant + rider
  • Restaurant dashboard is just another client of restaurant/order APIs

Happy path:

  1. Browse cached menus.
  2. Place order with idempotency key.
  3. Payment succeeds. Status waiting_for_restaurant. Notify kitchen.
  4. Restaurant accepts. Event → assignment worker.
  5. Rider accepts. Status assigned.
  6. Ready → picked up → delivered.
  7. Each hop appends order_events and pushes notifications.

Why a queue? Assignment and SMS should not sit inside the payment transaction. Same idea as Shopify-style event-driven design and message queues.


8. Deep Dives

Cart

Cart is ephemeral. At place-order, copy items into item_snapshot. After that, restaurant price changes must not alter this order.

Payment

Authorize first. Capture when the restaurant accepts (or at place-order if the business wants less drop-off). If the restaurant rejects, refund/void. Idempotency on payment ids. Talk about this like a fintech engineer, but keep the ledger in the payment service.

Restaurant accept / prepare

Timeout: if no accept in 3–5 minutes, auto-cancel and refund. Dashboard uses polling or a push channel. Reject reasons: too busy, out of item.

Delivery partner assignment

Reuse the geo idea from ride booking in a lighter form: nearby idle riders, sequential offer, expire, next rider. Do not block the order thread.

Status tracking

Customer reads orders + order_events. Real-time: pub/sub order:{id}. Restaurant dashboard lists open tickets by restaurant_id.

Inventory / menu availability

menu_items.available = false is enough for most interviews. For a 20-plate special, decrement a counter with a check at place-order. Oversell → restaurant rejects → refund.

Refund / cancellation

StateCustomer cancel
Waiting for restaurantFull refund
PreparingMaybe fees; policy
Picked upUsually no full refund
DeliveredSupport only

Write the policy. Implement as status checks, not as hope.

Notifications

Every public status change emits one event. Dedup so “preparing” is not sent twice. See notification system.


9. Bottlenecks

  • Hot restaurant at 8pm (one shop, thousands of carts)
  • Menu cache stampede when a restaurant toggles 50 items
  • Assignment when riders are scarce
  • Payment provider latency on place-order
  • Support tools scanning the main order table

Mitigations: per-restaurant rate limits, cache, timeout+refund, async payment webhooks if the provider is slow, replica for support reads.


10. Tradeoffs

ChoiceUpsideDownside
Capture pay before acceptRestaurants trust the orderMore refunds
Capture after acceptFewer refundsFake orders, restaurant risk
Strict item inventoryFewer 86sExtra complexity
Boolean availabilitySimpleOversell
Chatty WebSocketsLive UIConnection cost

Pick: idempotent place-order, pay then accept with timeout, events for assignment and notify, snapshot cart.


11. Failure Modes

FailureHandling
Payment succeeds, DB write failsIdempotency key + payment id reconciliation job
Restaurant never acceptsTimer cancels + refund
Assignment worker downOrder stays ready_for_assignment. Catch up from queue
Rider cancelsNew assignment, customer notified
Duplicate place-order tapSame Idempotency-Key returns the same orderId
Notification downStatus API still shows truth

Money: fail toward not cooking unpaid food, and refund if you cannot fulfill.


12. Interview Answer in 10 Minutes

"I would design food delivery as an order workflow, not as a single table update.

Assume 5 million DAU and 500,000 orders a day. Browse is about 100 million views, around 1,160 QPS, about 6,000 QPS at 5× peak — so cache menus. Place-order is only about 6 QPS average and 30 at peak. Storage is a few gigabytes a day. The design work is state, payments, and assignment.

Cart lives until checkout. Place-order uses an idempotency key, snapshots line items, and calls payment. On success the order waits for the restaurant. A timeout refunds. On accept, an event goes to a queue and an assignment worker offers nearby riders.

Restaurant dashboard, customer tracking, and notifications all read the same order status and event log. Inventory is at least an availability flag.

If the worker is down, assignment lags but the order is not lost. If the customer double-taps pay, they still get one order."

Practice this until it is under 10 minutes.


13. Interview Talking Points

  • State machine + event log.
  • Idempotent checkout.
  • Snapshot prices.
  • Async assignment.
  • Timeouts that refund.
  • Notifications are not source of truth.
  • Metrics: accept time, reject rate, assignment time, on-time delivery, refund rate, payment failures.
  • Classic Java / microservices workflow with Kafka.

14. Follow-up Questions

What if 2,000 people order the same restaurant?
Queue in the kitchen, longer ETA, pause the restaurant, or cap concurrent orders.

How is this different from Uber?
Food has a prepare delay before pickup. Matching too early makes riders wait at the door. Match when the meal is nearly ready.

Catalog search?
Separate from this workflow. Autocomplete is another question: search autocomplete.

Exactly-once assignment?
At-least-once offers + unique accepted partner on the order row (CAS).

Spring implementation?
Order service, outbox to Kafka, consumers for assignment and notify. See Pulse-Guard Kafka for idempotent consumers and scaling event-driven Java.


Related JavaThoughts reading:


Next in this series: Design a Payment System.