SwadhyAI Notes

All System Design notes

System Design Case Study

Designing Food Delivery Systems

A customer taps “Place order” at 7:40pm. Somewhere a kitchen tablet buzzes, a courier’s phone lights up with an offer, and a map starts moving. Underneath sits a whose hard constraint is time: cold fries and late couriers lose the product.

Design for dinner peaks, not average lunch. Keep an authoritative , revalidate at checkout, and treat as a timed marketplace decision—not a simple nearest-neighbor query.

Order → door journey

1

Validate menu, authorize, create order.

2

Accept, cook, signal ready.

3

Rank nearby, offer, optional batch.

4

Live map, capture, tip, rating.

What actually happens

Checkout is the correctness boundary. The cart may show yesterday’s price or an item the kitchen just 86’d. revalidates items, modifiers, fees, and taxes, authorizes payment, and writes the order in PLACED under one idempotency key.

The restaurant side is an ops product. Tablets receive push or long-poll events; acceptance starts the clock on . Dispatch should not fire the moment of payment: it should aim for courier arrival near ready, using prep estimates and live kitchen load. After assign, streams courier positions on an order channel.

Peak load is geographic and temporal. A at dinner can multiply candidate scoring work. Shard geospatial indexes by city, bound offer radius, and decide explicitly when is allowed. Payments capture on delivery (or per product policy); tips and refunds need their own idempotent keys.

How to choose the design moves

1. What must be exact? Paid orders, refunds, and “who owns this courier assignment” must be exact. Browse menus and map pins can lag a few seconds. Never let a stale menu cache authorize a charge.

2. When do you dispatch? Too early wastes courier time at the restaurant; too late cools food. Use plus travel-to-restaurant as the trigger, then refresh if the kitchen slips.

3. Solo vs batch? Batch when density is high and detours fit the . Otherwise solo. Make the policy measurable: on-time rate beats utilization vanity metrics.

4. Where is the authority? One region or shard should own the active order state. Location queries follow geography; state transitions must not race across two writers.

Simple rule of thumb

Treat food delivery as ride-matching plus a kitchen clock. Location freshness and offer timeouts come from mobility systems; menu holds, prep estimates, and order sagas come from marketplace checkout.

Case-study upgrade

E-Commerce Marketplace Math

An e-commerce marketplace can show stale catalog data, but checkout must revalidate price, inventory, and payment state.

Assumptions and formulas

  • Orders: 1M/day normally. Flash-sale peak: 10K checkout attempts/sec.
  • Product reads and search are far larger than successful purchases.
  • Inventory holds expire automatically and must be visible to checkout.

Concrete math

  • Orders: 1M/day = 11.6/sec average; flash sale can reach 10K/sec.
  • Product reads: 100M users x 20 views/day = 2B/day = 23K QPS average; peak can reach 70K to 200K QPS.
  • Cart writes: 100M users x 3 mutations/day = 300M/day = 3.5K/sec average.
  • Inventory holds: 10K checkout/sec x 10-minute hold = 6M concurrent holds during a flash sale.
  • Order events: 1M orders/day x 8 events x 500 bytes = about 4 GB/day raw.

Design decisions forced by numbers

  • Cache browse and search, but always revalidate authoritative inventory at checkout.
  • Use admission control for flash-sale checkout so inventory service stays within budget.
  • Make payment and order transitions idempotent and auditable.

Scaling to millions

  • At 1M users: catalog cache, cart store, inventory table, and order workflow are enough.
  • At 10M users: add search index, product cache warming, inventory holds, and async order events.
  • At 100M+ users: add waiting rooms, per-item hot partition isolation, and checkout degradation.

Readable flow

Browse/search
Product cache
Cart mutation
Inventory hold
Payment
Order confirmation

Interview follow-ups to expect

  • Which reads can be stale, and which checkout checks must be authoritative?
  • How do we prevent overselling a hot item?
  • What happens when payment succeeds but order confirmation times out?

Operational readiness checklist

After the math, explain how this capacity plan is measured, protected, launched, and evolved.

Observability

  • Attach each capacity estimate to a dashboard metric: p99 latency, queue age, cache hit rate, shard skew, error rate, and freshness lag.
  • Alert on rate-of-change and saturation, not only hard failures; backlogs and hot keys usually warn before an outage.

Security and abuse

  • Budget for auth checks, rate limits, quota isolation, audit logging, privacy filters, and abuse-review side paths.
  • Separate trusted internal events from user-controlled input so retries and backfills cannot bypass policy checks.

Degraded modes

  • Define the fallback when a dependency is slow: stale cache, partial results, read-only mode, delayed async work, or admission control.
  • Protect the invariant first; reduce freshness, personalization, or analytics quality before risking duplicate or corrupt state.

Testing and rollout

  • Test average, peak, and hot-object traffic; include retry storms, cold caches, lagging workers, and regional dependency loss.
  • Use canaries, shadow reads, dual-write validation, feature flags, and explicit rollback criteria for risky path changes.

Rollout, backfill, and runbooks

  • Make migrations and backfills idempotent, resumable, rate-limited, and visible through progress and error metrics.
  • Write the runbook before launch: owner, dashboards, paging thresholds, mitigation switches, replay steps, and repair commands.

10x evolution

  • For e-commerce marketplace math, state which number forces the next move: cache memory, shard count, write amplification, queue lag, egress, or failover reserve.
  • Add the next layer only when the metric forces it: sharding, regional reads, async replay, hot-key isolation, or stricter backpressure.

Scope you should nail down

Product surface

  • Browse restaurants and menus by location and cuisine
  • Cart, fees, tip, place order with payment
  • Restaurant accept / reject / prep updates
  • Courier offer, pickup, delivery, live map
  • Ratings, refunds, support dispute trail

Scale targets (example city → national)

  • ~50M users, ~5M orders/day nationally
  • Dinner peak ~5–10× average order rate in hot cities
  • Courier location update every ~3–5s while on trip
  • Promise ETA error and on-time rate as primary product SLOs

Capacity picture

58/s
Avg orders/sec
~500/s
Dinner peak (city cluster)
200K+
Live location updates/s

Orders: 5M/day ≈ 58/s average. A 8× dinner peak across active metros ≈ 460/s place-order attempts.

Dispatch scoring: 500 orders/s × 40 candidates ≈ 20K rank evaluations/s—cheap CPU, but geo index and offer fan-out dominate.

Tracking: 100K concurrent active trips × 1 update / 4s ≈ 25K location writes/s baseline; rain + events push higher.

Menu reads: Browse is cache-friendly; checkout validation hits restaurant-local availability stores.

Order lifecycle path

Diagram text: the exact Mermaid definition below preserves the nodes, labels, and connections. Enable JavaScript to view the rendered diagram.
Read the diagram definition
sequenceDiagram
    autonumber
    participant C as Customer app
    participant GW as API gateway
    participant O as Order service
    participant R as Restaurant service
    participant P as Payment
    participant D as Dispatch
    participant K as Courier app
    participant N as Notifications

    C->>GW: placeOrder(idempotency_key, cart)
    GW->>O: Create order PLACED
    O->>R: Validate menu + prep estimate
    R-->>O: OK + prep_minutes
    O->>P: Authorize payment (same key)
    P-->>O: authorized
    O->>N: Notify restaurant tablet
    R-->>O: restaurant_accepted
    O->>D: Schedule dispatch near ready
    D->>K: Offer assignment
    K-->>D: Accept
    O->>O: picked_up → en_route
    O->>N: Push live tracking to customer
    K-->>O: delivered
    O->>P: Capture payment + tip

Payment authorization happens before restaurant work burns kitchen capacity you cannot reverse cleanly. Capture (or final charge) usually waits until delivery or a clear cancellation policy.

Dispatch path

Diagram text: the exact Mermaid definition below preserves the nodes, labels, and connections. Enable JavaScript to view the rendered diagram.
Read the diagram definition
sequenceDiagram
    autonumber
    participant O as Order
    participant D as Dispatch
    participant L as Location index
    participant K as Courier
    participant B as Batcher

    O->>D: order ready_or_soon (restaurant geo)
    D->>L: GEORADIUS available couriers
    L-->>D: candidates + freshness
    D->>D: Rank ETA, load, rating, vehicle
    opt Dense zone
        D->>B: Try batch with nearby open orders
        B-->>D: batch plan or solo
    end
    D->>K: Offer with timeout
    alt Accepted
        K-->>D: accept
        D->>O: assign courier_id
    else Timeout / reject
        K-->>D: no
        D->>D: Next candidate / expand radius
    end

APIs worth sketching

typescript
searchRestaurants(lat, lng, filters?, page) → Restaurant[]
getMenu(restaurant_id) → categories, items, modifiers, availability
quoteOrder(cart) → fees, taxes, promise_eta, surge?
placeOrder(idempotency_key, cart, payment_method) → order_id
restaurantAccept(order_id) / restaurantReject(order_id, reason)
updatePrepEstimate(order_id, minutes)
courierUpdateLocation(courier_id, lat, lng, ts, seq)
courierRespondOffer(offer_id, accept|reject)
markPickedUp(order_id) / markDelivered(order_id)
getOrderTracking(order_id) → state, eta, courier_position

Core data model

Restaurants

sql
PK id
name, geo, hours
status (open/paused)
rating, cuisine_tags

MenuItems

sql
PK id · FK restaurant_id
name, price, modifiers
available bool
updated_at

Orders

sql
PK id
customer_id, restaurant_id
courier_id?
status, promise_eta
totals, created_at

OrderItems

sql
order_id, item_id
qty, unit_price
modifiers JSON

Couriers

sql
PK id
status, vehicle
rating, last_lat/lng
last_updated

Offers

sql
PK id · order_id
courier_id, expires_at
status, batch_id?

High-level architecture

Customer / Restaurant tablet / Courier apps
↓
API Gateway + realtime gateway
↓
Catalog / MenuOrderPricing / FeesPaymentDispatchLocationETANotification
↓
relational (orders)geo cacheevent log

Hard parts: menu, prep, and dispatch timing

Menu consistency. CDN or cache for browse; restaurant-scoped availability store for checkout. When an item is 86’d, invalidate that restaurant’s hot keys and reject new carts that still hold the item.

Prep estimation. Seed with restaurant-entered minutes; improve with historical prep by item mix and current open-order count. If the kitchen is late, widen for new orders and delay further dispatch into that restaurant.

Assignment claim. An offer is not ownership. Accept must conditionally move courier and order from expected states to assigned. A second dispatcher holding an old GEORADIUS result must lose the claim.

Dispatch claim sketch

plaintext
// Pseudocode: claim must be atomic
candidates = GEORADIUS(available, restaurant, radius) filtered by freshness
ranked = rank(candidates, eta_to_restaurant, load, rating)
for courier in ranked:
  offer = create_offer(order, courier, ttl=15s)
  if courier.accepts(offer):
    ok = CAS(
      order.status == READY_OR_SOON AND order.courier_id IS NULL,
      set courier_id, status=ASSIGNED
    ) AND CAS(courier.status == AVAILABLE → BUSY)
    if ok: return assignment
    else: continue  // lost race
expand_radius_or_queue(order)

Batching, ETA, and hot zones

  • Same neighborhood / short detour only
  • Hard cap on stops (often 2–3)
  • Recompute if any restaurant slips past threshold
  • Never batch across conflicting dietary cold-chain constraints if product forbids it

ETA ≈ max(prep residual, courier_to_restaurant) + restaurant_to_customer + buffer. Refresh after assign and after pickup; show confidence bands when variance is high.

Lengthen ETAs, pause low-priority promotions, raise delivery fees, or temporarily stop accepting orders into overloaded cells. Engineering side: shard geo indexes, rate-limit offer spam to couriers, and protect order writes with backpressure rather than silent timeouts.

Failure modes

Double place-order

Network retry after timeout. Mitigate with idempotency keys bound to payment authorization and order id.

Restaurant never accepts

Auto-cancel with refund after SLA; suggest alternatives. Do not dispatch a courier until acceptance (or a product-defined auto-accept policy).

Courier no-show / drop mid-trip

Detect via heartbeat + GPS stall. Re-dispatch with priority; preserve order history for support. Food may need remake—encode remake policy in the state machine.

Stale menu charge

Checkout revalidation failed open. Fail closed on availability; show a clear “item unavailable” recovery in the cart.

Location service degradation

Fall back to last-known fresh-enough pins for tracking UI; pause new matching in the worst cells rather than assigning ghosts.

Trade-offs

Early vs late dispatch

Early dispatch reduces pickup lag but burns courier idle time and worsens batching options. Late dispatch improves utilization but risks cold food. Tie the trigger to prep estimates and measure wait-at-restaurant minutes.

Batching vs on-time rate

Batching raises deliveries per courier-hour. It also correlates with ETA misses. Cap detours and kill a batch when one restaurant becomes an outlier.

Strong consistency for orders vs geo index speed

Keep order/payment transitions strongly consistent. Keep the available-courier index ephemeral and eventually consistent with heartbeats— but always re-check status at claim time.

Authorize-on-place vs charge-on-delivery

Authorize early protects kitchens from unpaid work. Capture late matches customer mental model and simplifies partial refunds. Hold expiry and auth windows must be designed with payment-provider limits.

Bottlenecks to call out

Dinner spike: queue non-critical side effects; protect place-order and dispatch paths first.

Tablet fan-out: restaurant notification storms need coalescing and durable delivery receipts.

Geo hot shards: split dense downtown cells; never put a whole metro on one Redis GEO key.

Realtime connections: regional WebSocket pools for tracking; polling fallback when blocked.

Choosing and explaining food delivery systems

A complete explanation connects the mechanism to a requirement. Describe what improves when you introduce it, which new failure cases become possible, and how you would know that the trade-off is worthwhile.

Explain the core idea

Frame it as a three-sided marketplace with an order state machine, not as “Uber for food” alone.

Call out the kitchen clock: dispatch timing relative to prep is the distinctive hard part versus pure ride-hailing.

Separate browse consistency from checkout and assignment claims.

When it belongs

Interviewer asks for a marketplace, logistics, or real-time tracking design with multiple actors.

You need to show geo indexing, idempotent payments, and stateful workflows in one story.

How it fails

Double orders from retries, ghost courier assignments, and stale menu charges.

Hot-zone overload causing cascading ETA lies and mass cancellations.

Trade-offs to weigh

Batching utilization versus on-time delivery.

Early courier arrival versus kitchen wait time.

Aggressive surge/ETA honesty versus short-term conversion.

Common misunderstandings

Designing only the customer app and ignoring restaurant acceptance SLAs.

Treating courier availability as durable truth instead of a soft index plus claim.

Skipping idempotency on place-order and tip capture.

Review the design

Three sides: customer checkout, restaurant prep, courier dispatch must share one order state machine.

Checkout: revalidate menu, authorize payment, idempotency key → single order.

Dispatch: time offers near ready; claim with CAS; optional batching under detour caps.

Tracking: ephemeral geo index + WebSocket channels; stale pins must not match.

Peaks: shard by city, protect place-order/dispatch, degrade honestly with longer ETAs.

Compare a starting answer with a more complete design

A starting answer

Model restaurants, menus, orders, couriers, payments, and a basic assign-nearest-courier flow with live tracking.

Developing the reasoning

Discuss prep-aware dispatch, batching constraints, hot-zone controls, idempotent checkout/payment, order ownership across regions, and ETA error as a first-class SLO.

Deeper walkthrough

food delivery deeper walkthrough

Food delivery couples marketplace checkout with local logistics. Menu correctness, kitchen timing, courier supply, and live tracking all interact under spiky geographic demand.

Requirement refinements

Clarify who can cancel when, tip timing, remake policy, multi-restaurant carts (usually out of scope), and scheduled orders.

State invariants: one paid order identity per idempotency key; one active courier owner per assigned order; available index is not ownership.

API design details

placeOrder requires idempotency_key; restaurant and courier transitions are explicit commands with authz by role.

Location updates carry device timestamps and monotonic sequence numbers to ignore delayed packets.

Data model and storage

Durable orders and payment records; ephemeral courier geo index with TTL; offer rows with expiry.

Append-only order_events for support, refunds, and dispute replay.

Scaling strategy

Partition location and dispatch by city/geohash; shard order history by customer_id or order_id.

Bound candidate set size; precompute busy-cell courier density for ETA and surge.

Reliability, recovery, and operations

Timeouts on restaurant accept and courier offers must auto-transition with customer-visible messaging.

Monitor on-time rate, ETA error, accept latency, dispatch success, wait-at-restaurant, and payment/order mismatch.

Shipping beyond the first design

The request flow explains normal operation. The next sections examine how you keep that behavior dependable after deployment: measuring user outcomes, limiting access, recovering from failures, and changing the system while people are using it.

Observability and SLOs

Dashboards by city and daypart; alert on on-time drop and dispatch failure spikes before generic CPU alerts.

Degraded modes and graceful fallback

Hide batching; lengthen ETAs; pause new orders in overloaded cells; tracking falls back to stage text without live map.

Primary sources