We’ve all watched the little scooter glide across the map without ever hitting refresh.
This post explains how food‑delivery apps stream a rider’s location to your phone in real time, the architecture choices behind it, and practical trade‑offs for accuracy, battery, and cost.
Core idea: Maintain a persistent connection to the client (WebSocket/SSE), publish rider GPS updates to a pub/sub backbone, and animate smoothly between points on the client.
flowchart TD
A[Driver App<br/>GPS + Sensors] --> B[Ingestion API<br/>Auth + Validation]
B --> C[Normalizer<br/>Map-Match + Throttle]
C --> D[(Pub/Sub Backbone<br/>Kafka/Redis/NATS)]
D --> E[Real-time Gateway<br/>WebSocket/SSE]
E --> F[Customer App/Web<br/>Marker Interpolation + ETA]
D --> G[Ops/Monitoring<br/>Dashboards, Alerts]
Objectives: accuracy when moving, battery savings when idle, resiliency on flaky networks.
{orderId, lat, lng, ts, speed, bearing, accuracy, seq} (seq = monotonically increasing).API receives updates, authenticates the rider, and normalizes for consistent downstream behavior.
{driverId, orderId, role}; rotate frequently.Use a backbone to fan‑out updates to all subscribers (customer app, restaurant, ops).
locations.order.{ORDER_ID} for targeted fan‑out.locations.driver.{DRIVER_ID} for ops/agent view.{orderId, seq}.Common choices: Kafka, Redis Streams/PubSub, NATS, RabbitMQ, or managed RT services (Pusher/Ably).
Keep a persistent connection open to the client and push location updates immediately.
Client (browser)
const ws = new WebSocket(`wss://rt.example.com?orderId=${orderId}&token=${jwt}`);
ws.onmessage = (e) => update(JSON.parse(e.data)); // {lat,lng,bearing,ts,seq}
ws.onclose = () => setTimeout(reconnect, 1000);
Server (Node/Express + ws pseudo)
wss.on('connection', (socket, req) => {
const {orderId, token} = parseQuery(req.url);
const sub = pubsub.subscribe(`locations.order.${orderId}`, msg => {
socket.send(JSON.stringify(msg));
});
const interval = setInterval(() => socket.ping(), 15000); // heartbeat
socket.on('close', () => { clearInterval(interval); sub.unsubscribe(); });
});
Server (Express)
app.get('/sse/orders/:orderId', auth, (req, res) => {
res.set({'Content-Type':'text/event-stream','Cache-Control':'no-cache','Connection':'keep-alive'});
const sub = pubsub.subscribe(`locations.order.${req.params.orderId}`, msg => {
res.write(`data: ${JSON.stringify(msg)}\n\n`);
});
req.on('close', () => sub.unsubscribe());
});
Goal: avoid “teleporting” markers and jitter.
ts.Example interpolation (pseudo):
function lerp(a:number,b:number,t:number){return a+(b-a)*t;}
function interpolate(p1, p2, now){
const t = Math.min(1, Math.max(0, (now - p1.ts) / (p2.ts - p1.ts)));
return { lat: lerp(p1.lat, p2.lat, t), lng: lerp(p1.lng, p2.lng, t) };
}
Driver → Ingest
POST /v1/driver/location
Authorization: Bearer <jwt>
Content-Type: application/json
[
{"orderId":"O123","seq":101,"lat":28.4575,"lng":77.0266,"bearing":210,"speed":6.4,"ts":1739251205123},
{"orderId":"O123","seq":102,"lat":28.4568,"lng":77.0240,"bearing":215,"speed":7.1,"ts":1739251207121}
]
Kafka topic config
# topic: locations.order
cleanup.policy=delete
retention.ms=600000 # 10 minutes
min.insync.replicas=2
Redis last-known state (TTL)
HSET order:O123 last '{"lat":28.4568,"lng":77.0240,"ts":1739251207121}'
EXPIRE order:O123 900
| Symptom | Likely Cause | Fix |
|---|---|---|
| Marker “teleports” | Out‑of‑order or bursty packets | Buffer by time window; sort by ts; use seq to discard old |
| Jitter on slow movement | GPS noise | Thresholding, Kalman filter, or map‑matching |
| High battery drain | Over‑frequent updates / high‑accuracy GPS | Adaptive intervals; only high accuracy when needed |
| Stale map after reconnection | Missed last state | On reconnect, fetch last-known + resume stream |
| Leaked streams after order end | Missing lifecycle hooks | Enforce server‑side TTL & server‑initiated unsubscribe |
Real‑time delivery tracking is a push‑based, event‑driven pipeline:
Driver App → Ingestion → Pub/Sub → Real‑Time Gateway → Customer UI
With the right guardrails—auth, buffering, interpolation, and observability—you get low‑latency, smooth tracking that feels instant without a single refresh.