Interview Setup
Interview Prompt
Design an ETA calculation service for a ride-hailing or navigation app. Given origin, destination, and mode, return accurate travel time estimates that reflect live traffic. Support single ETAs, batch driver-to-pickup queries, and live updates during an active trip.
Clarifying Questions (ask before designing)
| Question | Why it matters |
|---|---|
| What is the latency budget for a single ETA vs a batch of 20? | A 100ms single and 500ms batch SLO drives caching and compute trade-offs because the engine cannot afford to run full routing 20 separate times. |
| How accurate must ETAs be in terms of deviation from actual travel time? | A requirement within ±10% accuracy justifies an ML correction layer on top of the physics-based route engine. |
| Is this for matching (many drivers to one pickup) or passenger display? |
|
| How fresh must traffic data be? | 60-second freshness requires Flink aggregation on 10M GPS points/sec, not hourly historical averages. |
Scope
In scope
- Point-to-point and batch ETA APIs
- Live traffic ingestion and segment speed aggregation
- Contraction hierarchies routing with traffic weights
- ML correction from historical trip data
- H3 ETA matrix for coarse matching
- WebSocket ETA updates during active navigation
Out of scope (state explicitly)
- Map tile rendering and client display (covered in Map Rendering and Navigation)
- Driver dispatch and ride-matching orchestration (covered in Ride-Sharing Service)
- Turn-by-turn voice guidance
Functional Requirements
Confirm routing engine capabilities and traffic telemetry sources. The service requires point-to-point estimation, real-time traffic integration, batch queries for driver dispatch, and graceful fallback when telemetry lags.
In the room: when live traffic is stale, ETAs silently degrade, so explain how monitoring Flink consumer lag alerts the team to stale data.
- Point-to-point ETA: Estimate travel time with current traffic
- Multi-stop ETA: Routes with multiple waypoints
- ETA for pickup: How long until a driver reaches a pickup point
- Historical ETA: "How long does this trip usually take on Tuesday at 8 AM?"
- Batch ETA: Compute for N origin-destination pairs efficiently
- ETA updates: Continuously update as trip progresses
- Multi-modal: Driving, walking, cycling, transit
Non-Functional Requirements
ETA returned in under 500ms; traffic freshness under 60 seconds for ride-hail use cases.
- Accuracy: ETA within ±10% of actual travel time
- Low Latency: Single ETA in < 100 ms; batch of 20 in < 500 ms
- High Throughput: 500K ETA requests/sec peak
- Freshness: Traffic conditions reflected within 60 seconds
- Availability: 99.99%
- Graceful degradation: Fall back to historical patterns
- Global: Support all major cities
Capacity Estimations
Routing requests per second and road segment count drive graph storage and traffic aggregation.
| Metric | Calculation | Value |
|---|---|---|
| ETA requests / sec | From ETA requests / day ÷ 86400 (+ peak factor in value) | 500K peak |
| Single ETA latency target | Given (assumption documented in value) | < 100 ms |
| Batch ETA (20 pairs) target | Given (assumption documented in value) | < 500 ms |
| Active road segments tracked | Given (assumption documented in value) | 50M globally |
| Traffic updates / sec | From Traffic updates / day ÷ 86400 (+ peak factor in value) | 10M GPS data points |
| Road graph size (in-memory) | Given | ~300 GB globally |
| ETA model features | Given | ~50 per query |
| ETA cache hit rate | Given | ~30% |
Architecture Diagram
We combine a static road graph using Contraction Hierarchies with real-time segment speeds from GPS probes in Redis, apply traffic overlays to chosen routes, and return travel time estimates with confidence intervals. Batch matrix APIs answer multi-candidate queries in a single graph search, closely integrating with driver location streams from Real-Time Vehicle Tracking and tile vector geometries from Map Rendering & Navigation.
In the room: when Flink lag exceeds 60 seconds, ETAs drift silently, so emphasize alerting on traffic freshness SLO budget burn.
Component Deep Dives
Three-Layer ETA Architecture
Accuracy comes from stacking three layers: a fast graph router, real-time segment speeds aggregated from millions of GPS probes, and a machine learning correction model trained on historical trip outcomes. Each layer addresses a distinct source of estimation error.
Traffic telemetry streams from vehicle probes into streaming aggregations that continuously update Redis speed hashes. When stream workers lag, a multi-tier fallback hierarchy protects latency budgets without crashing dependent services.
ETA Calculation Pipeline: Three-Layer Architecture
Layer 1: Route Engine ETA (physics-based)
Find route via Contraction Hierarchies (< 1 ms)
For each segment: time = distance / speed (min of speed_limit, traffic_speed)
Layer 2: Traffic-Adjusted ETA
traffic_speed = Redis GET traffic:{segment_id} → current avg speed
If no traffic data: use historical avg speed
Add turn penalties (15-60s per major turn) and signal delays
Layer 3: ML Correction Model ⭐ (secret sauce)
Gradient Boosted Trees (XGBoost/LightGBM)
Features: route, traffic, temporal, weather, historical, spatial
Output: correction_factor
Final ETA = route_engine_ETA x correction_factorTraffic Aggregation Pipeline
1. GPS Ingestion (10M points/sec) → Kafka
2. Map Matching (Flink): GPS point → road segment
3. Segment Speed Aggregation (Flink, 60-sec window):
avg_speed = median(speeds), confidence = min(1.0, sample_count / 10)
4. Store in Redis: HSET traffic:{segment_id} speed {s} confidence {c}
TTL: 120 seconds
5. Fallback hierarchy: real-time → recent → historical → speed_limit x 0.7Batch ETA: Matching System Integration
Many-to-One Routing: Reverse Dijkstra from destination → find all 20 origins ~5 ms for 20 origins (vs 20 ms for separate queries) ETA Matrix (pre-computed): H3 cells (resolution 9 ≈ 175m), pre-compute cell-to-cell ETAs within 30 km ~500M pairs x 4 bytes = 2 GB per city → O(1) lookup Accuracy ±2-3 min, good enough for initial matching Refinement: After narrowing to top 3, compute exact ETA with full route + traffic
Event Bus Design (Kafka)
Topic: gps-traces
Partitions: 256 (scale Flink map-matching parallelism)
Partition key: vehicle_id (preserves per-vehicle GPS ordering for HMM map-matching)
Retention: 24h (high-volume telemetry at 10M points/sec)
Replication factor: 3, min.insync.replicas: 2
Producer: fleet GPS ingest (MQTT bridge, idempotent producer)
Event: { event_id, vehicle_id, lat, lng, speed, heading, timestamp }
Consumer groups:
1. traffic-aggregation: Flink map-match → 60s tumbling window median speed per segment
→ HSET traffic:{segment_id} {speed, confidence, updated_at} in Redis (TTL 120s)
2. trip-completion: completed trip actual_duration → ClickHouse for ML retrain
3. anomaly-detector: flag impossible speeds and GPS jumps for device health
Sync path (GET /eta): route engine + Redis traffic lookup + ML correction → return < 100ms
Async path: GPS never blocks ETA reads; Flink lag > 60s → degrade to historical speeds
DLQ: gps-traces-dlq after 3 retries; alert when consumer lag > 60sAPI Design
ETA Estimation and Streaming APIs
The service exposes endpoints for single point-to-point estimates, batch driver-to-pickup queries, coarse hexagonal matrix lookups, and WebSocket connections for active trip updates.
Single ETA
GET /api/v1/eta?origin_lat=37.7749&origin_lng=-122.4194&dest_lat=37.3382&dest_lng=-121.8863&mode=driving&departure_time=now
Response: 200 OK
{
"eta_seconds": 1920,
"eta_display": "32 min",
"distance_meters": 72400,
"confidence": 0.85,
"traffic_level": "moderate",
"breakdown": {
"route_engine_eta": 1680,
"traffic_adjustment": 180,
"ml_correction": 60
}
}Batch ETA
POST /api/v1/eta/batch
{
"origins": [
{"lat": 37.78, "lng": -122.41, "id": "driver-1"},
{"lat": 37.77, "lng": -122.43, "id": "driver-2"}
],
"destination": {"lat": 37.7749, "lng": -122.4194},
"mode": "driving"
}
Response: 200 OK
{
"etas": [
{"id": "driver-1", "eta_seconds": 240, "distance_meters": 1200},
{"id": "driver-2", "eta_seconds": 480, "distance_meters": 2100}
]
}ETA Matrix
GET /api/v1/eta/matrix?origin_h3=892830926cfffff&dest_h3=89283092e3fffffLive ETA Update (WebSocket)
// Server pushes every 60 seconds:
{
"type": "eta_update",
"session_id": "nav-uuid",
"remaining_eta_seconds": 1080,
"remaining_distance_meters": 35200,
"traffic_ahead": "moderate",
"route_changed": false
}Common Error Responses
400 Bad Request: invalid input, missing required fields, or malformed JSON payload 401 Unauthorized: missing or invalid authentication token or API key 403 Forbidden: authenticated caller lacks required permissions for this resource 404 Not Found: requested resource ID does not exist 409 Conflict: duplicate write or version conflict, retry with a unique idempotency key 422 Unprocessable Entity: syntactically valid request failed semantic business validation 429 Too Many Requests: rate limit quota exceeded, client should honor Retry-After header 500 Internal Error: unexpected server failure, retry safely with an idempotency key 503 Service Unavailable: downstream dependency is unavailable or overloaded, retry with exponential backoff 440 Login Timeout: WebSocket connection session expired, client reconnect is required
Data Model
Redis: Live Traffic + ETA Cache
traffic:{segment_id} → Hash { speed, confidence, updated_at } (TTL: 120s)
eta_matrix:{origin_h3}:{dest_h3} → INT (eta_seconds) (TTL: 300s)
eta_cache:{origin_geo6}:{dest_geo6}:{mode}:{hour} → INT (TTL: 300s)
hist_speed:{segment_id}:{dow}:{hour} → FLOAT (no TTL, updated weekly)ClickHouse: Historical Trip Data
CREATE TABLE completed_trips (
trip_id UUID,
origin_lat Float64, origin_lng Float64,
dest_lat Float64, dest_lng Float64,
origin_h3 UInt64, dest_h3 UInt64,
distance_meters UInt32,
predicted_eta UInt32, actual_duration UInt32,
departure_hour UInt8, day_of_week UInt8,
is_holiday UInt8,
weather Enum8('clear'=0,'rain'=1,'snow'=2,'fog'=3),
route_segments Array(UInt64),
mode Enum8('driving'=0,'walking'=1,'cycling'=2),
created_at DateTime
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(created_at)
ORDER BY (origin_h3, dest_h3, departure_hour, day_of_week);ML Model Store
Model: gradient_boosted_trees_v23.pkl 50M trips trained, 50 features, ~50 MB in memory Inference time: < 1 ms per prediction Retrained weekly, A/B tested before deployment
Fault Tolerance
| Concern | Solution |
|---|---|
| Traffic service down | Fall back to historical speed patterns |
| ML model error | Circuit breaker: bypass ML if latency > 50ms or error > 5% |
| Route engine crash |
|
| Stale traffic data |
|
| GPS data pipeline lag |
|
| ETA cache thundering herd | Singleflight: only compute once for concurrent identical requests |
Additional Considerations
Interview Walkthrough
- 25-minute cut
Skip arch50 and arch75 depth unless interviewing for a staff-level role.
- State latency budget: sub-100ms single ETA, sub-500ms batch matrix for driver matching (5 min)
- Three-tier pipeline: Haversine filter, Contraction Hierarchies query, and traffic overlay from Redis (6 min)
- Traffic ingestion: GPS probes into Flink aggregation with Redis traffic keys on 60 to 120s TTL (5 min)
- Expose batch ETA API for matching N drivers to one pickup without N separate graph searches (5 min)
- Staff only: live traffic staleness monitoring and client-side reroute on polyline deviation (4 min)
- Open with the latency budget: sub-100 ms single ETA and sub-500 ms batch, meaning every design choice must strictly justify its place in that latency envelope.
- Describe a three-tier pipeline: Haversine filter, Contraction Hierarchies road distance, and full routing with live traffic plus ML correction.
- Explain the traffic ingestion path: GPS probes, Flink streaming aggregation, and Redis segment speeds with TTL-based staleness fallbacks.
- Cover batch and matrix APIs for driver matching, including pre-computing H3-cell ETA matrices for hot corridors to avoid redundant routing computations.
- Mention caching with singleflight to prevent thundering herd on identical origin-destination pairs during rush hour.
- Discuss display smoothing and percentile ETAs (P50 for riders, P90 for delivery promises) to manage user trust when raw estimates jitter.
- Common pitfall: computing full routing for every candidate driver in a pool of 50 without the Haversine or Contraction Hierarchies pre-filter causes latency to blow past the SLA.
Live ETA Updates: Smoothing
displayed_eta = 0.7 x previous + 0.3 x raw Only show change when delta > 2 min or > 10% Why: GPS jitter → ETA fluctuates ±1 min Without smoothing: "17 min" → "18 min" → "17 min" → confusing
Time-Dependent Routing
Trip starts at 5:30 PM, arrives at 6:30 PM (rush gets worse) For each segment: estimated_arrival = departure + sum(previous_times) traffic_speed = predicted_speed(segment_id, estimated_arrival) predicted_speed: within 60 min → real-time + trend; 1-3h → blend 30/70; 3h+ → historical only
ETA Percentiles
Single ETA = 28 min doesn't capture uncertainty. Better: "28 min (25-35 min range)" Quantile regression: p10, p50, p90 models Rider display: P50 (median) Delivery promise: P90 (90% confidence) Matching: P50 for ranking, flag if P90 too high
Engineering Trade-offs
Filtering Hierarchy and Model Selection Trade-Offs
Balancing fast spatial heuristics, graph shortest-path searches, and statistical machine learning models allows the system to satisfy strict latency budgets while preserving high accuracy. Review caching trade-offs in Caching Patterns & Invalidation.
Haversine vs Road Distance vs Routing ETA
| Level | Method | Time | Accuracy | Use Case |
|---|---|---|---|---|
| 1 | Haversine | < 1 μs | 50-200% off | Initial filter (5 km) |
| 2 | Road Distance (CH) | < 1 ms | Distance correct, time rough | Rank top 20 |
| 3 | Routing ETA + Traffic + ML | 10-50 ms | ±10% | Final ETA to user |
XGBoost vs Deep Learning for ETA
XGBoost ⭐: - Tabular features → GBT excels (often beats DL on structured data) - Inference < 1 ms (vs DL: 5-50 ms) - Interpretable feature importance - Trains in minutes on 50M samples When DL wins: sequence modeling (LSTM on GPS trace), GNN for spatial dependencies Google Maps: GBT for base ETA + GNN for spatial traffic prediction
Review
How helpful was this walkthrough?
Click a star to rate. We actively use this feedback to refine and update our system design content.
Discussion
Share your thoughts, ask questions, or help others.