SDSystem design
Start learning free

LESSON 127 OF 140 · PRACTICE PROBLEMS

SYSTEM DESIGN · INTERVIEW WALKTHROUGH

Design a Package Order Tracking System: System Design Practice

The problem

"Design order tracking for a big online shop." A package leaves a warehouse, rides your trucks through hubs and depots, and in many cities a local carrier you don't run does the last miles. At every stop it is scanned, and the buyer follows it on a tracking page.

It reuses the pattern of Design Food Delivery, over days instead of minutes. New here: late scans, carriers with their own systems, a delivery day that moves, and far more lookups than scans. Park a live map of the van.

The numbers

From the interviewer: 10 million packages shipped a day, each scanned about 10 times and looked up about 100 times (the buyer, the shop's order page, apps). Round a day to 100,000 seconds (86,400 really).

what           working
scans          10 million × 10 ÷ 100,000 = 1,000 a second
lookups        10 million × 100 ÷ 100,000 = 10,000 a second (about 11,600 with 86,400)
busy hours     5 × 10,000 = 50,000 lookups a second
scans stored   100 million a day × 500 bytes = 50 GB a day, about 18 TB a year

Ten looks for every scan: the busy path is reading a status, not writing scans.

Late scans: the latest by scan time wins

Each scan is an append-only event: package, place, time, status. Scans don't always arrive in the order they happened. Say the Denver depot's scanner goes offline overnight and sends its Tue 22:00 scan at Wed 08:30:

scan time   what                 arrived
Mon 09:00   picked up, Reno      1st
Mon 21:00   at Salt Lake hub     2nd
Tue 06:00   left Salt Lake hub   3rd
Wed 08:10   out for delivery     4th
Tue 22:00   at Denver depot      5th, late

The status is the scan with the latest scan time: out for delivery. The last scan to arrive would put the package back at the depot while the van is at the door. Refusing the late scan, as Food Delivery's state machine refused a step out of order, is wrong too: it really happened, and a package can really go back (a failed delivery returns to the depot).

Carriers you don't run: webhooks or polling

A carrier with webhooks calls your URL on every scan; handle it as in the Webhooks lesson: check its signature, answer fast, skip repeats by event id. A carrier with only an API ("where is package X?") needs a poller. Say carrier B carries 180,000 of your packages and you ask about each every 30 minutes: 180,000 ÷ 1,800 s = 100 calls a second, a rate you agree with the carrier. The poller turns its words into your statuses and skips scans already saved.

Calling the carrier on each page view puts its rate limit and slow minutes into your page, and "delivered" is noticed only when someone looks. A nightly file is too slow, but a good check that nothing was missed.

The delivery estimate: typical times per leg

Learn each leg's typical time from past packages, and add the legs ahead to the latest scan's time:

leg                        typical time
Reno → Salt Lake hub       12 h
Salt Lake → Denver depot   25 h
Denver depot → door        16 h

Picked up at Mon 09:00, a package is due 12 + 25 + 16 = 53 hours later, Wed 14:00: the promised Wednesday. Say a snowstorm holds package p77 and it reaches the Salt Lake hub at Tue 21:00, a day late: 25 + 16 = 41 hours later is Thu 14:00, and the buyer is told it's delayed. Each new scan restarts the sum: if p77 reaches the depot at Wed 18:00, the estimate becomes Thu 10:00.

The promised day never moves, so it warns nobody. Distance ÷ speed (about 500 miles at 50 mph, 10 hours) ignores the hours a package waits at hubs.

Notifications: key changes only

A message per scan is 100 million messages a day, and buyers turn them off. Send only on key changes: out for delivery, delivered, an estimate moved to a later day, a failed attempt. Hub scans, same-day changes and late scans only update the page. That's about 2 messages for most packages, 20 million a day, each sent the Notification System's way: a queue and workers per channel, retries, no duplicates.

Many lookups: cache the current status

At 50,000 lookups a second, a database server that answers about 5,000 is asked 10 times what it can do. Keep a status cache: one entry per package (status, place, time, estimate), written by the scan path when a newer scan lands. Nearly every lookup is a hit, and one cache server answers about 100,000 a second. A package is tracked for about 5 days, so 50 million entries × 500 bytes = 25 GB: it fits in memory.

A copy kept for an hour would show "out for delivery" after the buyer was told "delivered". Working the status out from all the scans on each look reads 500,000 rows a second, 100 times one database server.

Same pattern as Food Delivery

Every change becomes an event on a stream keyed by package id, and each reader (notifications, the estimate, the cache) skips repeats by event id. What changes is time: an order lives 30 minutes and every change is news; a package lives days, most scans aren't news, and facts arrive late, from systems you don't run.

Summary

  • Scope: scans over days, other carriers, a moving estimate, many looks.
  • Data: append-only scans; the status is the latest by scan time.
  • API: scans and carrier webhooks in, GET /track/p42 out.
  • Numbers: about 1,000 scans a second and 50,000 lookups at busy hours.
  • Services: a poller for carriers without webhooks; messages on key changes only.
  • Scale: each package's status cached, updated by every scan.
  • Estimate: the latest scan plus the typical time of each leg ahead.
Keep going with system designInteractive lessons from the building blocks to Twitter, Uber and payments, the way interviews ask. Free to start.
Start learning free