Company questionsGroundworkZomato

System design · Chat and real time

Design live order tracking for a food delivery app

  • Listed in prep guides
  • 2 diagrams
  • 2 sources

Prep guides list it as a common prompt; no candidate write-up was found.

The prompt
Design the live tracking screen for a food delivery app: the customer sees the order status and the rider moving on a map. Assume 2 million orders a day, most of them in a two-hour dinner window, and riders on flaky mobile networks.

A representative prompt in the style reported, not a quote from Zomato. The answer below is written for this site.

Try it first.The answer is covered until you pick. Talk it through against a 45 minute clock, or just read it.

Ask first

Clarify the problem before you draw anything.

First questions: is tracking read-only for the customer, or does it also drive the rider app? How fresh must the dot be: every second, or every 5 seconds with smooth animation? Does one rider carry one order or several? What happens when a rider loses signal? Is the ETA part of this system, or does a routing service own it? Do we keep location history for disputes?

I would assume one order per rider, a 5-second ping, an ETA service we call but do not build, and short history kept for support. In scope: rider location ingest, order status, push to the customer, reconnect. Out of scope: rider assignment, route planning, the restaurant side and payments.

Numbers

A back-of-envelope that decides the shape.

These are planning figures for the exercise, not facts about any company.

  • Orders: 2 million a day. If 40% land in a two-hour window: 800,000 ÷ 7,200 s = about 111 orders a second.
  • Live orders: assume an order lives 35 minutes (2,100 s) and the rider is moving for the last 25 (1,500 s). Riders on the road: 111 × 1,500 = about 165,000.
  • Location writes: a ping every 5 s from 165,000 riders is 165,000 ÷ 5 = 33,000 updates a second, at 100 B each about 3.3 MB a second, or 3.3 MB × 7,200 s = 24 GB over the peak window.
  • Viewers: if half of those orders have the map open: 82,500 connections. At 20 KB each that is 1.65 GB, which fits in one gateway box; I run 3 for redundancy. Pushes: 82,500 ÷ 5 = 16,500 a second.
  • ETA recompute: every 30 s per rider is 165,000 ÷ 30 = 5,500 a second if done on a timer.

33,000 location writes a second is more than six times the 5,000 committed writes a second planning figure, so pings must not go through the relational database. Only the last position matters, so an in-memory store with latest-wins semantics fits. Order status changes (111 orders a second times about 6 states is under 700 a second) belong in the database.

The design

Diagrams first, then components, data and the hard parts.

Live tracking at a glancePings never touch the order database. A log and a latest-wins store carry location; status joins the same stream from the order service.
Rider appIngest gatewayauth, validate, appendLocation logby rider_idTracking workerdrops stale seqLatest positionrider:{id}, TTL 60 sOrder channelpub/sub per orderPush gatewayWebSocket or SSECustomer appOrder serviceorders table, outboxping + seq, ~5 sappendone per partitionwrite if seq higherpublishsubscribedposition streamstatus via outbox12345678
  1. The rider app batches pings, one every ~5 s, each with a per-device increasing seq.
  2. The ingest gateway authenticates, validates and appends to the location log. It never touches the order database.
  3. The log is partitioned by rider_id, so one worker owns each partition and one rider's pings stay in order.
  4. The worker writes (lat, lng, ts, seq) to the latest-position store only if seq is higher than the stored one.
  5. It then publishes the ping to the pub/sub channel for that rider's active order.
  6. The push gateway subscribes to the channel of every order that has a connected customer.
  7. The customer's one stream carries position updates to the map.
  8. Status changes (placed, accepted, ... delivered) come from the order service through an outbox into the same channel.

Open on the whiteboardAll 2 diagrams on one tidy board, with a step-by-step walkthrough.

Components. Rider app; an ingest gateway; a partitioned log; a tracking service; an in-memory store for the latest position; a push gateway (WebSocket or server-sent events) for customers; the order service with its relational database; and an ETA service.

Data model.

  • Order database: orders(order_id, customer_id, rider_id, state, state_version) with states placed, accepted, preparing, ready, picked up, delivered. This is the source of truth for status.
  • Latest-position store: key rider:{rider_id}, value (lat, lng, ts, seq), TTL 60 s. Sharded by rider id (hash), because every read and write names a rider.
  • Location log: partitioned by rider_id, so one rider's pings stay in order. Retained a few days for support and replay.

Write path: location.

  1. The rider app batches pings and sends one every ~5 s, each with an increasing seq that survives an app restart (for example a boot counter plus a sequence number). Stationary riders ping less often.
  2. The ingest gateway authenticates, validates, and appends to the log. It never touches the order database.
  3. A tracking-service worker per partition applies the ping to the latest-position store only if seq is higher than the stored one, so late or duplicated pings are dropped.
  4. The worker looks up the rider's active order (a small local map fed by order events) and publishes to a pub/sub channel for that order.

Read path: customer. Opening the screen does two things in order: fetch a snapshot by REST (status, last position, its seq), then open a stream. The push gateway subscribes to the channel for each connected order and forwards messages. Status changes come from the order service through an outbox into the same channel, so the customer gets one stream carrying both. Pub/sub can drop a message without the connection closing, so each status message carries state_version; the client re-fetches the snapshot when it sees a gap or when no status has arrived for a minute.

ETA. Do not re-route on every ping. Recompute on state change, on a 30 to 60 s timer, and when the rider drifts off the expected route. In between, the client counts down locally.

Hard problem 1: volume without a database. Latest-wins plus a log gives cheap writes, replay, and no lock contention. Losing an in-memory shard loses at most seconds of data, because the next ping repopulates it.

Hard problem 2: flaky networks. Mobile connections drop constantly. The client reconnects with its last seq and re-fetches the snapshot, so it never needs the missed stream. If a position is older than ~30 s, show "rider's location is updating" rather than a frozen dot that looks live. Interpolate between pings on the client for smooth movement.

Consistency. Status needs read-your-writes: "delivered" must come from a committed row, never from a guess. Location may be seconds stale and is eventually consistent. The two share a stream but not a store.

Failure. A gateway box dies: its clients reconnect to another and take a snapshot. A worker dies: its partition is reassigned and the next pings fix the state.

Trade-offs

What you chose, and when you would flip.

  • Push stream vs polling. Polling every 5 s from 82,500 viewers is 16,500 requests a second, stateless and simple. A stream costs connection state but sends only changes and feels live. Pick the stream for the map; keep polling as a fallback for bad networks and for the status-only screen. Flip to polling for a small audience or when the gateway fleet is the bottleneck.
  • Latest-position store vs the database. The store takes 33,000 writes a second easily but is not durable. Pick it because only the newest value matters. Flip to a database only for the slice that must be kept, such as sampled history for disputes.
  • Fixed vs adaptive ping rate. A fixed 5 s is predictable. An adaptive rate (faster near the customer or at a turn, slower when stationary) saves battery and writes. Start fixed; flip to adaptive when battery complaints or ingest cost show up.
  • Fan-out by order channel vs by gateway. Per-order channels are easy to reason about but create many subscriptions. Flip to partition-per-gateway routing when subscription count becomes the cost.

Follow-ups to expect

Where the interviewer pushes next.

  1. A rider's phone goes offline in a tunnel for two minutes and then sends 24 buffered pings at once. What does the customer see?
  2. How would you show several orders for a rider who carries two deliveries at once?
  3. How do you test this at 33,000 pings a second before the dinner rush?
  4. How would you keep a long-running customer stream alive through a deployment of the push gateway?
  5. How would you reduce battery drain on the rider app without making the map look stale?

Where candidates slip

The mistakes this question punishes.

  • Writing every ping to the main order database. It is 33,000 writes a second against a primary that handles 5,000.
  • Treating location and status as the same data. Status must be strongly consistent; location is a hint that can lag.
  • No sequence number, so a late ping moves the dot backwards.
  • Recomputing the ETA on every ping through a routing engine.
  • Forgetting reconnect: a design that only works while the connection stays up.
  • Showing a frozen position as if it were live when the rider has gone offline.
  • Opening one connection per customer with no plan for a gateway restart, so every deployment drops thousands of live screens at once.

Have a go before you read.

Interviews reward the sketch you make cold. Clarify out loud, draw it, then compare with the answer.

Now make it stick

Where this was reported

Public pages only. Companies do not confirm these, and they rotate their questions, so treat each link as one person's account or one list's tag.

Read these chapters first