Introduction
Logistics tracking systems process millions of scan events daily, maintain accurate package state, and surface real-time location to customers. Designing this system requires a reliable event pipeline, a strict shipment state machine, accurate ETA prediction, and multi-channel customer notifications — all at carrier scale.
Shipment State Machine
A shipment moves through a defined sequence of states: LABEL_CREATED → PICKED_UP → IN_TRANSIT → OUT_FOR_DELIVERY → DELIVERED, DELIVERY_ATTEMPTED, or EXCEPTION. Transitions are triggered by barcode scan events emitted from warehouse scanners and driver mobile apps. The state machine enforces valid transitions — invalid progressions such as moving from DELIVERED back to IN_TRANSIT are rejected at the consumer service level before any write occurs. Each accepted transition produces a TrackingEvent record and may trigger downstream notifications.
Schema
Shipment (shipment_id, tracking_number, sender_id, recipient_address, weight_grams, dimensions, service_level, status, created_at) — the canonical record for a package from label creation to final disposition.
TrackingEvent (event_id, shipment_id, event_type, location, facility_id, scan_device_id, created_at, raw_scan_data) — immutable append-only record of every scan. Stored in Cassandra for high-throughput append and time-series access patterns.
Facility (facility_id, name, address, lat, lng, type ENUM origin/hub/sort_center/delivery_station) — represents every physical location in the carrier network. Used to resolve facility_id on scan events to human-readable location strings.
Scan Event Pipeline
Warehouse scanners and driver apps publish scan events to a Kafka topic named scan-events. The topic is partitioned by shipment_id so all events for a given shipment are consumed in order by a single partition. A consumer service reads each event, validates the payload, applies the state machine transition, writes the TrackingEvent to Cassandra (optimized for append-heavy time-series workloads), and updates Shipment.status in PostgreSQL. After a successful write, the consumer publishes a normalized event to a notifications Kafka topic for downstream processing. Cassandra’s wide-row model makes it efficient to fetch all events for a tracking number in chronological order.
ETA Prediction
An ML model is trained on historical delivery times keyed by (origin_zip, dest_zip, service_level, carrier, day_of_week, time_of_day, volume_index). Features are refreshed on each scan event as the package’s location and transit history become more informative. The predicted ETA is stored on the Shipment record and updated after every scan. When ML model confidence falls below a threshold, the system falls back to rule-based estimation — for example, standard service level = current hub scan timestamp + 2 business days. This fallback prevents nonsensical ETAs during model degradation or data gaps.
Last-Mile Tracking
Once a driver begins their delivery route, the driver mobile app emits GPS coordinates every 30 seconds. Locations are written to Redis GEO under key driver:{driver_id}:location with a short TTL so stale positions expire automatically. The customer-facing tracking page either polls the location API at an interval or connects via Server-Sent Events (SSE) to receive push updates and render the driver’s position on a map. The driver’s stop sequence is planned by a routing optimizer using a TSP (Travelling Salesman Problem) approximation, balancing delivery windows and geographic clustering.
Customer Notifications
A notification service consumes the scan-events Kafka topic independently of the tracking pipeline. A rule engine maps each event_type to a notification template and determines which channels to use: push notification, SMS via Twilio, or email. To suppress noise, a deduplication layer checks a Redis key before sending — if the same event_type for the same shipment was sent within the last 30 minutes, the notification is skipped. The Redis key is set with a 30-minute TTL on first send. A delivery attempt failure event triggers a reschedule flow, prompting the customer to select a new delivery window or pickup location.
Handling Exceptions
EXCEPTION events carry a reason_code field with values such as address_not_found, access_denied, damaged, or weather_delay. Each exception triggers an escalation workflow: the customer is notified immediately, rescheduling is offered, and the system tracks attempt count. After 3 failed delivery attempts, the shipment status transitions to a return-to-sender state and the reverse logistics flow is initiated. Exception reason codes are also aggregated for carrier operations dashboards to identify systemic issues — high address_not_found rates may indicate a data quality problem in the address normalization layer.
Frequently Asked Questions: Package Logistics Tracking System
How should a shipment state machine handle invalid transitions in a logistics tracking system?
Invalid transitions should be rejected at the service layer before any persistence occurs. Define an explicit allowed-transitions map (e.g., CREATED -> PICKED_UP -> IN_TRANSIT -> OUT_FOR_DELIVERY -> DELIVERED) and throw a domain exception for any other edge. Log the attempt with the shipment ID, current state, and requested state for auditability. Return a 409 Conflict to callers so upstream systems can alert operations teams rather than silently dropping the event.
When should you choose Cassandra over PostgreSQL for storing package tracking events?
Cassandra is the better fit when write throughput dominates and queries are always by a known partition key such as shipment ID with a time-range scan on the clustering column. It handles millions of append-only tracking events per second with linear horizontal scale and no single point of failure. PostgreSQL is preferable when you need ad-hoc joins, strong consistency across tables, or complex aggregate queries that span many shipments. For a high-volume carrier, Cassandra wins on the hot path; PostgreSQL can serve the reporting replica.
What features are most useful for ML-based ETA prediction in a package tracking system?
The most predictive features are: current leg elapsed time vs. historical median for that route segment, number of remaining stops on the delivery route, time of day and day of week, weather severity index for the destination zip, carrier facility throughput (packages scanned per hour at the last hub), vehicle load factor, and historical on-time rate for that carrier-route pair. Include a rolling 7-day deviation feature to capture seasonal drift. Feed these into a gradient-boosted model retrained nightly on completed deliveries.
How do you implement real-time last-mile GPS tracking using Redis GEO commands?
Store each driver’s position with GEOADD fleet:{date} longitude latitude driver_id on every GPS heartbeat (typically every 5-30 seconds). Clients poll or subscribe via Server-Sent Events; the backend calls GEOPOS to fetch the driver’s coordinates and GEODIST to compute distance to the delivery address. Use GEORADIUS or GEOSEARCH to find all drivers within a radius for dispatch. Set a TTL on the key so stale positions expire automatically. Keep the write path fire-and-forget; durability is not critical since the next heartbeat overwrites the value.
How do you design a notification deduplication window for shipment status updates?
Use a Redis key with the pattern notif:{shipment_id}:{event_type} and SET NX EX {window_seconds} before dispatching each notification. If SET NX returns nil the notification was already sent within the window; discard it. Choose a window of 5-15 minutes for status transitions like OUT_FOR_DELIVERY. For delivery confirmation, use a permanent flag stored in the shipment record instead of a TTL key so the customer is never notified twice even across Redis restarts. Log all suppressed notifications so operations can audit missed sends.
See also: Scale AI Interview Guide 2026: Data Infrastructure, RLHF Pipelines, and ML Engineering
See also: Uber Interview Guide 2026: Dispatch Systems, Geospatial Algorithms, and Marketplace Engineering
See also: Shopify Interview Guide