System Design blueprint · Food delivery
Food delivery: order to doorstep
One order crosses payments, the restaurant, rider dispatch and live tracking, and must never be lost, charged twice or left without a rider.
- Steps
- 8
- Subjective questions
- 31
- Objective questions
- 31
The problem and the outcome
Problem: At dinner peak, thousands of orders a minute must be paid, accepted by restaurants and matched to nearby riders, while customers watch every step live.
Outcome: Orders confirm in about a second, riders are assigned within a minute, and a failed payment or a rejecting restaurant refunds automatically.
Architecture
How it works, step by step
- The customer places the order with cart, address and payment method; the app sends an idempotency key so a retry never creates a second order.
- The API gateway authenticates the user, rate-limits per account and routes to the order service.
- The order service re-checks prices and that the restaurant is open, then writes the order (PENDING) and an outbox event in one database transaction.
- Payment is authorised through the gateway with the same idempotency key; the gateway's webhook confirms it, and a failure cancels the order (a saga compensation).
- A relay publishes OrderPaid from the outbox to Kafka, partitioned by order ID, so each order's events stay in order.
- The restaurant accepts and sets a preparation time; a rejection or a timeout triggers an automatic refund.
- Dispatch finds nearby free riders with a Redis geo query, scores them by distance, ETA and current load, and offers the order with a timeout before re-offering.
- Riders' GPS pings update the geo index; the tracking service pushes live position and ETA to the customer over WebSockets, and notifications fire on each status change.
Tech stack
- API gateway (Kong, Envoy or AWS API Gateway)
- Order, payment, restaurant and dispatch microservices
- PostgreSQL with a transactional outbox
- Kafka (or Amazon MSK)
- Redis geo index (GEOADD / GEOSEARCH)
- WebSocket gateway for live tracking
- Payment gateway with webhooks (UPI, cards)
- Push (FCM / APNs) and SMS providers
Key design decisions
- A saga orchestrated by the order service instead of one distributed transaction: each step (pay, accept, assign) has a compensating action such as refund or cancel.
- A transactional outbox: the order row and its event are written in one database transaction and relayed to Kafka, so an event is never lost or sent for an order that was rolled back.
- Idempotency keys on order creation and payment, so a retry after a timeout never creates two orders or charges twice.
- Rider locations live in an in-memory geo index (Redis) with short TTLs, not in the orders database: they change every few seconds and only the latest point matters.
- Kafka topics partitioned by order ID: events for one order are processed in sequence, while different orders scale across partitions.
Common mistakes
- Calling payment, restaurant and dispatch synchronously in one request, so one slow partner times out the whole checkout.
- Publishing to Kafka after the database commit without an outbox, losing events whenever the process crashes in between.
- Treating the payment gateway's redirect as proof of payment instead of verifying its signed webhook or querying its status API.
- Offering one order to many riders at once without a lock, so two riders arrive for the same food.
- Writing every GPS ping to the primary database, which turns the busiest data into the most expensive writes.
What to measure
- Order confirmation p99 latency (target: about a second)
- Time to rider assignment (target: under a minute at peak)
- Payment success rate and duplicate-charge count (target: zero)
- Restaurant acceptance rate and time to accept
- On-time delivery rate: actual vs promised ETA
Most-asked interview questions
Subjective questions 31
1Design a food delivery platform like Swiggy or Zomato: walk through what happens from 'Place order' to delivery.Asked almost every time
The app sends the order with an idempotency key to the API gateway, which routes it to the order service. That service validates prices and opening hours, writes the order and an outbox event in one transaction, and starts a saga: authorise payment, then publish OrderPaid to Kafka. The restaurant service gets it and the restaurant accepts with a prep time; dispatch queries a Redis geo index for nearby riders, scores them and offers the order with a timeout. Riders' GPS updates the index, and a tracking service pushes live ETA over WebSockets while notifications fire on each status change. Failures run compensations: refund if the restaurant rejects, re-offer if a rider declines.
2How do you make sure a customer is never charged twice for one order?Asked almost every time
Send an idempotency key from the client on order creation and reuse it for the payment request, so retries return the same order and the same payment attempt. Store payment attempts with a unique constraint on that key, treat the gateway's signed webhook as the source of truth, and make the webhook handler idempotent because gateways retry. A daily reconciliation job compares the gateway's settlement report with your records and refunds any duplicates it finds.
3What are the functional and non-functional requirements of a food delivery platform?Asked almost every time
Functional: browse restaurants and menus, build a cart, place and pay for an order, restaurant acceptance, rider assignment, live tracking, ratings, cancellations and refunds. Non-functional: high availability at meal-time peaks, order confirmation in about a second, no lost orders and no double charges, consistent order state across services, low-latency location updates, and graceful degradation when a partner (payment gateway, SMS provider) is slow.
4Which services would you split the system into, and why?Asked almost every time
User and auth, restaurant and menu catalogue, search, cart, order (the saga orchestrator), payment, restaurant-partner, dispatch, rider, tracking, notification, pricing and promotions, and ratings. They change at different speeds, scale differently (search and tracking are read- or write-heavy at peak) and are owned by different teams, so splitting them lets each scale and deploy on its own.
5Why use a saga instead of a distributed transaction for an order?Asked almost every time
An order touches payment, the restaurant and dispatch, which are separate services and often external partners that cannot join a two-phase commit. Holding locks across them for seconds would also kill throughput. A saga runs each step as a local transaction and defines a compensating action for each (refund, cancel, release rider), so the system ends consistent without global locks.
6Orchestration or choreography for the order saga: which would you choose?Asked almost every time
Orchestration: the order service holds the state machine (PENDING → PAID → ACCEPTED → ASSIGNED → PICKED_UP → DELIVERED) and tells each service what to do next. The flow is easy to read, change, monitor and time out in one place. Choreography, where services react to each other's events, suits simple flows but becomes hard to follow as steps and compensations grow.
7Explain the transactional outbox and why the order service needs it.Asked almost every time
Writing to the database and then publishing to Kafka are two separate operations; a crash between them loses the event, or a publish before a rollback announces an order that never existed. With an outbox, the order row and an event row are written in one local transaction, and a relay (polling or change-data-capture such as Debezium) publishes outbox rows to Kafka. Consumers must still be idempotent, because the relay can publish an event more than once.
8How do you handle the payment gateway's webhook safely?Asked almost every time
Verify the signature with the gateway's secret, look up the payment by its order or payment ID, and process it idempotently: store the event ID with a unique constraint so a retried webhook is a no-op. Respond 200 quickly and do the real work asynchronously. Never trust the client's redirect as proof of payment, and if a webhook is late, query the gateway's status API before cancelling.
9How do you store and query live rider locations?Asked almost every time
Riders send GPS every few seconds to a location service that writes the latest point to a geo index: Redis GEOADD keyed by city, or riders bucketed into H3 or geohash cells. Entries carry a short TTL or a last-seen time, so offline riders drop out. Dispatch queries by radius around the restaurant with GEOSEARCH. A sample of pings goes to a stream for analytics and ETA models, never to the primary orders database.
10How would you scale rider dispatch for a whole city at peak?Asked almost every time
Shard dispatch by city or zone, because a match never crosses zones. Within a zone, collect new orders over a short window (a few seconds) and solve the assignment as a batch, scoring rider–order pairs by pickup ETA, prep time, load and fairness, which beats greedy one-by-one matching at peak. Keep a lock per order and per rider so one offer is outstanding at a time, and re-run matching for declined or timed-out offers.
11How would you assign the best rider to an order at dinner peak?Very often asked
Keep live rider positions in a geo index (Redis GEOADD with short TTLs, or H3 cells). On OrderPaid, query riders within a radius of the restaurant, filter by status and vehicle, then score by pickup ETA, the restaurant's remaining prep time, current load and fairness. Offer to the top rider with a 20–30 second timeout and a lock on the order, re-offering to the next on decline. At peak, batch orders from the same restaurant and assign in short windows to optimise globally rather than greedily.
12A customer taps 'Pay' and the network drops. What happens next?Very often asked
The app retries with the same idempotency key, so the server returns the existing order and payment attempt instead of creating new ones. The order stays PENDING until the gateway's webhook or a status poll says success or failure. If nothing arrives within the timeout, a background job queries the gateway; a captured payment moves the order on, and a failed or abandoned one cancels it.
13How do you compute and keep the delivery ETA accurate?Very often asked
ETA = time until food is ready (from the restaurant's prep time and history) + rider travel to the restaurant + travel to the customer, using a routing engine and live traffic, corrected by a model trained on past deliveries. Recalculate on every status change and on fresh GPS, smooth the number so it does not jump around, and show a range rather than a single minute when confidence is low.
14How does live tracking reach the customer's phone?Very often asked
The app opens a WebSocket (or Server-Sent Events) connection to a tracking gateway once the order is assigned. The gateway subscribes to that order's location updates (Redis pub/sub or a Kafka topic keyed by order) and pushes position and ETA every few seconds. Connections are spread across gateway nodes behind a load balancer, and the app falls back to polling if the socket drops.
15What happens if the restaurant does not respond to a new order?Very often asked
The restaurant service starts a timer when it delivers the order to the partner app, with reminders such as a ring or a call from an automated system. If there is no acceptance within the limit, the saga cancels the order and refunds automatically, and the restaurant may be marked temporarily unavailable so new customers do not order from it.
16How do you stop two riders being sent to the same order?Very often asked
Make assignment a compare-and-set on the order: set the rider only if the order is still unassigned (a conditional update or a Redis lock with an expiry), and offer to one rider at a time with a timeout. A rider's accept call must also pass that check, so a late acceptance after a re-offer fails cleanly and the rider is told the order is gone.
17How would you design the menu and restaurant catalogue for heavy reads?Very often asked
Menus change rarely but are read constantly, so serve them from a cache and a CDN-backed API, with the catalogue service as the source of truth. Restaurants publish changes that invalidate or update cached menus through events. Availability (out of stock, closed) is a small, fast-changing overlay checked again at checkout, so a stale cache can never sell an item that is gone.
18How do you handle a dinner-time spike that is ten times normal traffic?Very often asked
Auto-scale the stateless services ahead of known peaks, keep the hot reads (menus, search) on caches and CDNs, and queue the work that can wait (emails, analytics). Kafka absorbs bursts between services. Shed load gracefully: rate-limit per user, switch off expensive features such as personalised recommendations, and widen the dispatch batching window instead of failing orders.
19What data store would you use for orders, and how do you partition it?Very often asked
A relational database such as PostgreSQL for orders and payments, because they need transactions and constraints (unique idempotency keys, valid state transitions). Partition or shard by order ID or by city as volume grows, keep recent orders hot and archive old ones, and serve history and analytics from read replicas or a warehouse rather than the primary.
20How do you keep order status consistent across all the services?Very often asked
One owner: the order service holds the state machine and is the only writer of the order's status. Other services emit events (PaymentCaptured, OrderAccepted, RiderAssigned) and the order service applies valid transitions only, rejecting impossible ones such as DELIVERED before PICKED_UP. Events carry the order ID and a version, so out-of-order or duplicate events are detected and ignored.
21How do you handle a customer cancelling midway?Very often asked
Cancellation is a saga step whose effect depends on the state: before acceptance, a full refund; after the restaurant starts cooking, a partial or no refund, per policy; after pickup, usually not allowed. The order service records the cancellation, runs the matching compensations (refund, notify restaurant, release the rider) and each step is idempotent, so a retried cancel does nothing twice.
22How do you design search for restaurants and dishes?Very often asked
Index restaurants and dishes in a search engine (Elasticsearch or OpenSearch) with geo fields, cuisine, rating, price and availability, and filter by delivery radius first. Rank by relevance, distance, ETA, rating and business rules. Keep the index updated from catalogue events, cache popular queries per area, and fall back to a simpler list if search is degraded.
23What would you monitor to know the platform is healthy during peak?Very often asked
Business signals first: orders per minute against the forecast, payment success rate, restaurant acceptance time, time to rider assignment and on-time delivery. Then system signals: p99 latency per service, Kafka consumer lag per topic, WebSocket connection counts, error rates and dead-letter queue growth. Alert on the business signals, because a drop in orders per minute catches problems that no single service metric shows.
24How do you reconcile payments with orders?Very often asked
Run a reconciliation job that matches the gateway's settlement and refund reports with your payment and order records by ID and amount. Flag mismatches: captured but no order, order paid twice, refund not settled. Fix them automatically where it is safe (refund a duplicate) and send the rest to an operations queue. This catches webhook losses and bugs that idempotency alone cannot.
25How do you make the consumers of order events safe to retry?Very often asked
Kafka delivers at least once, so each consumer records the event IDs it has processed (or uses a natural key with a unique constraint) and skips duplicates. Side effects are idempotent: 'set status to ACCEPTED' rather than 'toggle', 'refund for payment X' with a unique refund key. Poison messages go to a dead-letter topic after a few retries so they never block the partition.
26How would you test this system before a festival sale?Very often asked
Load-test with replayed and synthetic traffic at several times the expected peak, including payment-gateway mocks with realistic latency and failures. Run chaos experiments: kill a dispatch node, delay webhooks, add Kafka lag, and check that sagas compensate and nothing is lost. Practise the runbooks, pre-scale, and freeze risky deploys for the event.
27How are notifications sent without slowing the order flow?Often asked
Status changes publish events; a notification service consumes them, picks the channel (push first, SMS for critical steps or when push fails), applies user preferences and templates, and sends asynchronously through providers. Failures are retried with backoff and dead-lettered. Nothing in the order path waits for a notification to be delivered.
28How do you price delivery fees and surge?Often asked
A pricing service computes the fee from distance, basket value, demand and rider supply in the zone, and promotions. The quote is calculated at checkout, signed or stored with an expiry, and honoured when the order is placed, so the customer pays what they saw. Surge rises gradually with the demand-to-supply ratio and is capped, with the rules logged for audit.
29What happens when a rider's phone goes offline mid-delivery?Often asked
The rider's last position stays visible with a 'last updated' time, and the ETA widens. Dispatch watches for a missed heartbeat; after a threshold, operations are alerted and the customer is told. If the rider reconnects, the app syncs queued status updates (with timestamps) idempotently. If not, support can reassign or contact the rider.
30How do you handle multiple orders for one rider (batching)?Often asked
When two orders come from the same restaurant or lie along one route, dispatch can assign them to one rider if both promised ETAs still hold. The matching score includes the detour for each customer, a cap of two or three orders, and food-quality limits on extra minutes. Each order keeps its own tracking and status even though the rider is shared.
31What are the main trade-offs in this design?Often asked
Eventual consistency between services in exchange for availability and independent scaling, so the UI must show in-between states. Batch dispatch gives better matches but adds seconds of delay. Caching menus is fast but needs an availability check at checkout. Sagas avoid global locks but need careful compensations and idempotency everywhere. Good answers name the trade-off and why it is acceptable here.
Objective questions 31
1Which pattern guarantees an order's event reaches Kafka exactly when the order row is committed?Asked almost every time
Why: The event is written to an outbox table in the same transaction as the order, and a relay publishes it, so the two can never disagree.
2A restaurant rejects an order after the customer has paid. In a saga, what should happen?Asked almost every time
Why: Sagas undo completed steps with compensating actions; here that means an automatic refund.
3Why does order creation carry an idempotency key from the client?Asked almost every time
Why: Networks fail after the server has acted; the key lets the server recognise the retry and return the first result.
4In an orchestrated saga, who decides the next step after PaymentCaptured?Asked almost every time
Why: Orchestration puts the flow in one service, which reacts to each result and issues the next command or compensation.
5Kafka delivers order events at least once. What must consumers be?Asked almost every time
Why: Duplicates are expected with at-least-once delivery, so processing the same event twice must have no extra effect.
6Which Redis command finds riders within 3 km of a restaurant?Very often asked
Why: GEOSEARCH (the successor to GEORADIUS) queries members of a geo set by radius or box around a point.
7Which is the compensating action for 'payment captured' when the order cannot be fulfilled?Very often asked
Why: Compensation undoes the business effect of a completed step; for a capture that is a refund, recorded with its own key.
8Why partition the order topic by order ID?Very often asked
Why: Kafka guarantees order only within a partition, so keying by order ID keeps each order's events in order.
9What is the source of truth that a UPI or card payment succeeded?Very often asked
Why: Redirects can be faked or lost; only a verified webhook or a server-side status check confirms the payment.
10Where should live rider locations be stored for dispatch queries?Very often asked
Why: Locations change every few seconds and only the latest matters, which suits an in-memory geo index.
11What prevents two riders accepting the same order?Very often asked
Why: A compare-and-set makes the second acceptance fail cleanly instead of double-booking.
12What does the outbox relay do?Very often asked
Why: It moves committed events to Kafka, by polling or change-data-capture, decoupling the write from the publish.
13Why is batch matching better than greedy matching at peak?Very often asked
Why: Greedy assigns the nearest rider now and can leave later orders badly served; a short batch optimises the whole zone.
14A restaurant has not accepted an order within the limit. What should the saga do?Very often asked
Why: A timeout is a failure of that step, so its compensations run and the customer is refunded.
15Kafka consumer lag on the dispatch topic keeps growing. What does it mean?Very often asked
Why: Lag is the gap between the latest offset and the consumer's position; growth means consumers need to scale or are stuck.
16Who should be the only writer of an order's status?Very often asked
Why: A single owner applies only valid transitions, so services cannot leave the order in contradictory states.
17What does daily payment reconciliation catch that idempotency cannot?Very often asked
Why: Comparing settlement reports with your records finds payments that never reached your system or were recorded wrongly.
18A webhook arrives twice for the same payment. What should the handler do?Very often asked
Why: Gateways retry webhooks, so storing the event ID with a unique constraint makes the duplicate a no-op.
19Which protocol suits pushing a rider's live position to the customer's app?Often asked
Why: A persistent connection lets the server push updates every few seconds without the app polling.
20Which store fits orders and payments best?Often asked
Why: Orders need atomic writes, valid states and unique idempotency keys, which relational databases enforce.
21Menus are read constantly but change rarely. What is the usual approach?Often asked
Why: Caching serves the reads cheaply; the checkout check protects against selling an item that just ran out.
22Which metric best shows dispatch is keeping up at peak?Often asked
Why: Slow assignment is the first sign that riders are short or matching is overloaded.
23Where should a message that fails processing again and again go?Often asked
Why: A dead-letter topic stops one bad message from blocking the partition while keeping it for later.
24Why shard dispatch by city or zone?Often asked
Why: Matching is local, so splitting by zone divides the load without cross-zone coordination.
25What does GEOADD do in Redis?Often asked
Why: GEOADD stores positions in a sorted set encoded for geo queries such as GEOSEARCH.
26Why send most notifications asynchronously through a queue?Often asked
Why: Decoupling lets the order path finish at once while the notification service retries with backoff.
27Why should the delivery-fee quote be stored with an expiry?Often asked
Why: Honouring a recent quote keeps the price consistent; the expiry stops a very old quote being used at a different demand level.
28During a 10× spike, which feature is a sensible one to switch off first?Often asked
Why: Load shedding drops nice-to-have features to protect the core path of ordering, paying and delivering.
29What should the app show while an order is between PAID and ACCEPTED?Often asked
Why: Services are eventually consistent, so the UI shows honest intermediate states rather than pretending the order is final.
30Batching two orders on one rider is allowed when…Often asked
Why: Batching saves riders, but only if neither customer's delivery is delayed beyond the promise.
31Which test best proves the saga's compensations work before a sale?Often asked
Why: Injected failures show that timeouts, retries and compensations leave every order in a correct final state.
Track your progress through every blueprint in your dashboard.
Start learning