Want it harder? Hide the model answer and work each phase in a scratchpad against its time budget, then reveal and score yourself. Or simply read on: the checkpoints below work either way.
How Do You Find the Fastest Way Through a Billion Road Segments, While the Roads Keep Changing Speed Under You?
It is 5:40 pm in Bengaluru. Ananya has a flight at 8:30 from Kempegowda International Airport, 38 km north of her flat in Koramangala. She opens the map app and types "airport". In a fifth of a second she sees three routes: one over the Hebbal flyover, one along the Outer Ring Road through Hennur, and a longer one on the toll road. The first says 1 hr 12 min.
She leaves at 5:45. At 6:02 a truck breaks down on the Outer Ring Road, a few kilometres ahead of the cars that took the second route. Ananya isn't on it, but the app has to know within a couple of minutes, from nothing but the phones crawling past the truck, and send everyone behind them another way. And the 1 hr 12 min it promised at 5:40 had to account for the Hebbal jam that only forms at 6:15, when she would get there.
- 2 billion+ — people using Google Maps every month (Miriam Daniel, Google Maps, 6 February 2025)
- 1 trillion km — of directions Google Maps provided in 2024; 250 million businesses and places on the map (same post)
- 100 million — updates made to the map every day (same post)
- 1 billion km — driven with Google Maps every day, in more than 220 countries and territories (Google, September 2020)
- 97%+ — of trips with accurate arrival-time predictions; DeepMind's graph neural networks then improved real-time ETA accuracy by up to 50% in some cities (Google and DeepMind, September 2020)
- 360 billion — raster tiles it would take to cover the world at 20 zoom levels, one reason Maps moved to vector tiles that need "more than 100 times less data" (Google, December 2010)
For context:
Google hasn't published which shortest-path algorithm Maps uses; the routing deep dive uses the published research (contraction hierarchies and customizable route planning) that, per a 2015 survey, OSRM and Bing Maps use
Google doesn't publish request or probe rates, so the estimates below state their own assumptions
Everything not in the list above is an illustrative design target, not Google's internal architecture
This is the system we'll design today.
Google Maps has 2 billion+ monthly users, gave 1 trillion km of directions in 2024 and makes 100 million map updates a day. On stated assumptions ~3 million phones navigate at peak, sending ~600,000 probe points a second, and routing answers ~61,000 queries a second, where plain Dijkstra (2.2 s on a continental graph) would need ~134,000 cores. The road graph is partitioned once into cells with a shortcut overlay (CRP), whose costs are recomputed from live speeds in under a second; phone fixes are map-matched into per-segment speeds within about 2 minutes; ETAs time each road for when the driver will reach it; vector tiles are named by quadkey and cached immutably by map version. The deep dives cover routing, live traffic, ETAs and tiles, then herding, sparse and fake probes, closures and a monsoon evening.
The Interview Begins#
You're interviewing at a company building a navigation app. The interviewer opens a map on the screen, drags it across a city, zooms into a junction, then types two addresses. Three coloured lines appear, each with a time. One of the roads on the map is red.
"Three things on this screen are the product: the map you can drag, the routes with their times, and that red road. Design what makes them."
People pan and zoom a map of the whole world, ask for driving directions between any two points, and drive with turn-by-turn guidance. Routes and arrival times must reflect traffic now and the traffic expected later on the trip, and the map must load quickly on any phone and network.
Requirements
- Show a map of the world at every zoom level, quickly, on phones and the web
- Driving directions with alternatives, in well under a second, at continental distances
- Live traffic from phones on the road, fresh within a couple of minutes
- An arrival time that is right, for now and for "leave at 6 pm"
- Reroute drivers when the road ahead closes or slows
"Leave out transit, walking, place search, reviews and Street View. Roads, traffic, time, tiles."
Phase 1: Requirements Clarification (5 minutes)#
"This is three systems sharing one road graph: a routing engine, a traffic pipeline and a tile service. I want numbers for each."
Your Questions#
"How many people?"
"Google said in February 2025 that Maps has more than 2 billion users a month. It doesn't publish a daily figure; assume a quarter of them open it on a given day."
"How much driving?"
"In 2020 Google wrote that people drive more than 1 billion kilometres a day with Google Maps, in over 220 countries and territories. In 2025 it said it gave 1 trillion kilometres of directions in 2024."
"Where does traffic come from?"
"Phones. Google's 2009 post on crowdsourced traffic says it uses 'anonymous speed and location information' from phones that opted in, combines reports from the same area, and deletes 'the start and end points of every trip'. Its 2020 post adds historical traffic patterns, authoritative data from local governments, and incident reports from drivers."
"How good does the arrival time have to be?"
"Google said in 2020 its ETAs were 'consistently accurate for over 97% of trips', and that work with DeepMind then improved real-time ETA accuracy by up to 50% in some cities, 51% in Taichung. Aim for that."
"And how often does the map itself change?"
"100 million updates a day, from imagery, partners and the 500 million people who contribute reviews, photos and reports. Most are places; some are roads opening and closing."
Functional Requirements#
- Map: pan and zoom anywhere, from the whole world to a single lane; works on poor networks; offline areas downloadable
- Directions: driving routes between any two points, with two or three alternatives, turn restrictions, tolls and ferries as options
- Traffic: a speed for every road with enough probes, coloured on the map; incidents and closures from reports and authorities
- ETA: an arrival time when asked, updated continuously while driving, and for a chosen departure time
- Navigation: turn-by-turn guidance; reroute when the driver leaves the route or a faster one appears
- Map updates: new roads, closures and turn restrictions reach routing and tiles within hours, temporary closures within minutes
Non-Functional Requirements#
Latency (design targets)
- Directions: p99 < 300 ms server time, including alternatives and ETAs
- Map tile from the nearest edge: p99 < 100 ms; a new city on screen in under 1 s
- A new jam reflected in routing: within about 2 minutes of cars slowing
Scale
- 500 million daily users (assumed a quarter of 2 billion monthly)
- ~3 million phones navigating at the busiest hour (estimated below)
Privacy and accuracy
- Probes are anonymous; trip start and end points deleted; no road shown as live with too few devices
- ETA within a few minutes for 97% of trips
Phase 2: Back of the Envelope Estimation (5 minutes)#
"Google doesn't publish request rates, so every rate below is an assumption built on the published totals. The shape is what matters: probes are the biggest stream, route checks outnumber new requests, and tiles are mostly cache hits."
Before the numbers: how many phones are navigating at rush hour?
1 billion km a day are driven with Google Maps. Assume an average speed of 40 km/h and that the busiest hour carries 3 times the average hour. How many phones are navigating at that hour? If each sends a location every 5 seconds, how many probe points arrive a second? If each asks for a better route once a minute, how many route checks is that?
One strong answer
- Driving time: 1 billion km ÷ 40 km/h = 25 million hours a day.
- On average, 25 million ÷ 24 ≈ 1 million phones navigating at once; at a 3× peak, ~3 million.
- Probes: 3 million ÷ 5 s = 600,000 points a second at peak.
- Route checks: 3 million ÷ 60 s = 50,000 a second, many times the rate of new direction requests. Most routing work is re-checking routes people are already driving.
Traffic Estimation#
| Value | |
|---|---|
| Driving with Maps (Google, 2020) | 1 billion km a day |
| Driving time at 40 km/h | 25 million hours a day |
| Phones navigating, average / peak hour | ~1 million / ~3 million |
| Probe points (one every 5 s), peak | ~600,000 a second |
| Probe points a day | ~18 billion |
- New directions: 1 trillion km of directions in 2024 is ~2.7 billion km a day; at an average 15 km a route, ~180 million requests a day, ~2,100 a second, ~6,300 at peak
- Route checks while navigating: once a minute per phone, ~50,000 a second at peak
- Reroutes after a missed turn: one per driver every 10 minutes, ~5,000 a second
- Total route queries: ~61,000 a second at peak; design for 100,000
Conclusion: the routing engine answers about 100,000 queries a second, almost nine times more from drivers already on the road than from people asking for the first time.
Routing Compute Estimation#
- The benchmark graph: Western Europe, 18.0 million junctions and 42.5 million directed road segments
- Dijkstra's algorithm: scans 9.3 million junctions and takes 2.2 s for a random query on one core
- Searching from both ends (bidirectional Dijkstra) roughly halves that: 4.9 million junctions, 1.2 s
- At 61,000 queries a second: 61,000 × 2.2 s = ~134,000 cores busy, before alternatives
- With a preprocessed overlay (CRP): 1.65 ms for the distance, 1.81 ms with the path; assume ~10 ms of CPU per request for three alternatives and their ETAs: ~610 cores
- With contraction hierarchies (CH):0.11 ms a query, but every change of road speeds means contracting the graph again (about 2 minutes on 12 cores, without turn costs)
Conclusion: preprocessing buys a speed-up of three orders of magnitude, but traffic changes the costs every minute. The routing design has to keep the preprocessing that doesn't depend on speeds and redo only the cheap part.
Storage Estimation#
- World road graph: assume ~25 times the Western Europe benchmark: ~450 million junctions, ~1 billion directed segments (estimate)
- Routing data (CRP): 3.11 GiB for Western Europe plus 0.07 GiB per cost function; ~25 such regions is ~80 GiB, each region fitting in one server's memory
- Live speeds: 1 billion segments × 2 bytes = ~2 GB per snapshot
- Historical speed profiles: 1 billion segments × 7 days × 96 quarter-hours × 1 byte = ~670 GB
- Raw probes: 18 billion points × ~40 bytes = ~720 GB a day, kept briefly; trip ends deleted
Conclusion: everything routing touches fits in memory. The probe stream is the large, fast-moving input, and it is turned into a few gigabytes of speeds.
Tile Estimation#
- Raster tiles: Google wrote in 2010 that it takes more than 360 billion tiles to cover the world at 20 zoom levels
- Vector tiles, zoom 0 to 16, the phone drawing deeper zooms itself: 4^16 ≈ 4.3 billion tiles at zoom 16, ~5.7 billion at all levels; ~30% with land data, ~1.7 billion tiles × ~20 KB ≈ 34 TB per map version
- Fetches: 500 million daily users × 2 map sessions × 40 tiles = 40 billion tiles a day, ~460,000 a second, ~1 million at peak: ~20 GB/s, about 160 Gbps
- Origin load: at a 95% CDN hit rate, ~50,000 tile reads a second reach storage
- Map updates: 100 million a day is ~1,160 a second; each geometry change dirties one zoom-16 tile and its 16 ancestors
Conclusion: tiles are a read-mostly cache problem. The hard part is not serving them but changing them: a closed road must reach routing in minutes, while the tiles can follow.
Key Metrics Summary#
| Metric | Value |
|---|---|
| Daily users (assumed) | 500 million |
| Phones navigating at peak | ~3 million |
| Probe points at peak | ~600,000/s |
| Route queries at peak | ~61,000/s (design for 100,000) |
| Routing CPU, Dijkstra vs CRP | ~134,000 cores vs ~610 |
| Live speeds snapshot | ~2 GB |
| Tile fetches at peak | ~1 million/s, ~160 Gbps |
Requirements and estimation
Q1/3 1 billion km are driven with Maps a day. At an average 40 km/h, how many hours of driving is that?
Answer & explanation
Answer: B
1 billion ÷ 40 = 25 million hours. Spread over 24 hours with a 3× peak, that's about 3 million phones navigating at once.
Q2/3 3 million navigating phones each ask for a better route once a minute. How many route checks a second?
Answer & explanation
Answer: B
3 million ÷ 60 = 50,000 a second: far more than the ~6,300 new direction requests at peak.
Q3/3 Why can't the routing engine run plain Dijkstra on every request?
Answer & explanation
Answer: C
Dijkstra is correct but scans millions of junctions per query. Preprocessing cuts that to a few thousand.
Phase 3: High-Level Design (10 minutes)#
Sketch it first: Ananya asks for the airport
Trace Ananya's request from her phone to the three routes on her screen, and then her drive. Which data has to exist before she asks? What reads live traffic? Where do the map tiles she sees come from, and where does the red colour on the Outer Ring Road come from? What does her phone send while she drives?
One strong answer
- Before she asks: the road graph is partitioned and its overlay built (slow, done when the map changes); the overlay's costs are recomputed from live speeds every few seconds (fast); tiles for the map version are built and cached at edges.
- The request: her phone sends origin, destination and options to the API; the routing service for her region runs the query on the overlay with current costs, finds three alternatives, and asks the ETA service to time each along the speeds expected when she'll reach each road.
- The map: her phone already has vector tiles for Bengaluru from the CDN; the route lines and a small traffic overlay with a short expiry are drawn on top.
- While she drives: every 5 s her phone records a location, sending a batch every 30 s. Map matching turns those into "this phone crossed this road segment at this speed", the aggregator updates segment speeds, and the next customization round makes routes avoid the slow roads.
- Once a minute, her phone asks whether a faster route exists; a closure ahead triggers a reroute.
"Three loops share one road graph: a slow loop that rebuilds the graph and tiles when the map changes, a fast loop that turns probes into road speeds and route costs every few seconds, and the request path that only reads."
System Architecture#
%% title: Routing, traffic and tiles architecture
flowchart TD
subgraph L1["Layer 1 · Clients"]
direction LR
AND@{ icon: "tech:android", label: "Android app" }
IOS@{ icon: "tech:apple", label: "iOS app" }
WEBC@{ icon: "sd:browser", label: "Web" }
end
subgraph L2["Layer 2 · Edge"]
direction LR
CDN@{ icon: "sd:cdn", label: "CDN<br/>vector tiles, traffic overlay" }
API@{ icon: "sd:api", label: "API gateway<br/>directions, probes, tiles" }
end
subgraph L3["Layer 3 · Map data (slow loop)"]
direction LR
MAPDB@{ icon: "sd:database", label: "Map data<br/>versioned road graph" }
TB["Tile builder<br/>dirty tiles, per version"]:::service
TS@{ icon: "sd:object-storage", label: "Tile store<br/>immutable, by version" }
end
subgraph L4["Layer 4 · Traffic (fast loop)"]
direction LR
KP@{ icon: "tech:kafka", label: "Kafka · probes<br/>by region" }
MM@{ icon: "tech:flink", label: "Flink · map matching<br/>and segment speeds" }
SPD@{ icon: "tech:redis", label: "Redis · live speeds<br/>per segment" }
HIST@{ icon: "sd:database", label: "Historical profiles<br/>per segment, per 15 min" }
end
subgraph L5["Layer 5 · Routing"]
direction LR
CUST["Customizer<br/>speeds to overlay costs"]:::core
ROUTE["Routing service<br/>one region per server"]:::hot
ETA@{ icon: "sd:ml", label: "ETA service<br/>time-dependent + model" }
end
AND ==> API
IOS ==> API
WEBC ==> API
AND ==>|"tiles"| CDN
CDN --> TS
MAPDB -.->|"new version"| TB
TB --> TS
MAPDB -.->|"graph + partition"| ROUTE
API ==>|"probe batches"| KP
KP ==> MM
MM ==> SPD
HIST -.-> ETA
SPD --> CUST
CUST -->|"costs every few s"| ROUTE
API ==>|"directions"| ROUTE
ROUTE --> ETA
SPD -.->|"overlay tiles"| CDN
class AND,IOS,WEBC client
class CDN,API edge
class MAPDB,TS,SPD,HIST store
class KP queue
class MM,ETA serviceKEY INSIGHTS:
- Split preprocessing by what it depends on. The partition and overlay shape depend only on the map, so they change rarely; the overlay's costs depend on traffic, so they change every few seconds and must be cheap to recompute
- Probes are a stream, speeds are a table. 600,000 points a second come in; a few gigabytes of segment speeds come out, and that table is all routing reads
- Tiles are immutable by version; the live part of the map (traffic colours) is a separate overlay with a short expiry, so the base map can be cached for days
Data Flow: Ananya's Request, Then Her Drive#
%% title: Directions for Ananya, then probes from her drive sequenceDiagram autonumber participant A as Ananya's app participant G as API gateway participant R as Routing, Bengaluru region participant E as ETA service participant K as Probe stream participant M as Map matching participant S as Live speeds A->>G: Koramangala to the airport, now G->>R: Route query with current overlay costs R->>R: Search source cell, overlay, target cell R->>E: Three alternatives, 38 to 44 km E->>E: Speeds expected when she reaches each road E-->>A: Hebbal 1 hr 12 min, ORR 1 hr 15, toll road 1 hr 19 Note over A: she drives, a fix every 5 s A-)G: Batch of 6 fixes every 30 s G-)K: Append to the region's partition K-)M: Fixes to road segments M-)S: Segment speeds, each minute
MEANWHILE, IN PARALLEL:
- The customizer recomputes the Bengaluru overlay's costs from live speeds every few seconds, so the next query, Ananya's minute-by-minute route check included, sees the slowdown
- Her phone draws the map from cached vector tiles and refreshes only the small traffic overlay for the tiles on screen
High-level design
Q1/3 Which part of the routing preprocessing has to be redone when traffic changes?
Answer & explanation
Answer: C
The partition and overlay topology depend only on the map. Only the costs depend on speeds, and they are recomputed in well under a second.
Q2/3 Why keep traffic colours in a separate overlay instead of baking them into the map tiles?
Answer & explanation
Answer: B
Mixing a value that changes each minute into a tile that changes each week would make the whole tile uncacheable.
Q3/3 Ananya's app sends a batch of six location fixes every 30 seconds. What does the rest of the system want from them?
Answer & explanation
Answer: B
Map matching turns fixes into segment traversals; the aggregator turns many phones' traversals into one speed per segment.
Phase 4: Deep Dives (20 minutes)#
Deep Dive 1: Routing at Continental Scale (Week 1 Concepts: Partitioning; Week 4: Precomputation and Caching)#
"A road network is a graph: junctions are vertices, road segments are directed edges with a travel time. The textbook answer is Dijkstra's algorithm. The real answer is to precompute enough that a query only looks at a few thousand junctions, and to split that precomputation so traffic doesn't force us to redo it."
This builds on Week 1, Day 1 · Partitioning Deep-Dive.
Dijkstra across a continent
Someone in Mumbai asks for directions to Delhi, about 1,400 km. Dijkstra explores outward from Mumbai in rings of equal travel time, settling every junction closer than Delhi: on the Western Europe benchmark, a random query settles 9.3 million junctions and takes 2.2 seconds. What could you precompute so that a query touches only a few thousand junctions? And what happens to that precomputation when a highway slows down?
One strong answer
- Observe the hierarchy: long routes quickly leave local streets for a small network of highways. Nearly every long route uses the same few important roads.
- Contraction hierarchies (CH): rank junctions by importance, remove the least important one at a time, and add a shortcut edge wherever removing it would break a shortest path. A query searches upward only, from both ends, and meets near the top. On Western Europe: 5 minutes of preprocessing, 280 junctions scanned, 0.11 ms per query.
- The catch: a shortcut's cost is the sum of the roads it skips. When traffic changes those roads, the ranking and shortcuts may no longer be right, so CH must contract again: about 2 minutes on 12 cores for travel times, 23 minutes with turn costs.
- Customizable route planning (CRP): partition the graph into cells and build, for each cell, an overlay of shortcuts between its boundary junctions. The partition and the overlay's shape don't depend on speeds; only the shortcut costs do, and recomputing them took 0.37 s on 12 cores for Western Europe (36 ms on GPUs). Queries take 1.65 ms.
- For live traffic, choose CRP's split: a slow metric-independent phase when the map changes, a sub-second customization whenever speeds change.
What is published: the 2015 survey "Route Planning in Transportation Networks" (Bast, Delling, Goldberg, Müller-Hannemann, Pajor, Sanders, Wagner, Werneck) compares these techniques on the same 18-million-junction Western Europe graph and concludes that "one can compute driving directions in milliseconds or less even at continental scale". It reports that Bing Maps uses CRP and that OSRM, the open-source engine for OpenStreetMap, uses CH. Google hasn't published its own algorithm. CRP was designed for what production needs: turn costs and restrictions, several cost functions (fastest, shortest, avoid tolls), queries whose speed doesn't depend on the cost function, and costs that change with traffic.
%% title: Customizable route planning, in three layers
flowchart TD
MAP@{ icon: "sd:database", label: "Road graph<br/>junctions, segments, turns" }
PART["Partition, metric-independent<br/>cells with few boundary roads"]:::service
TOPO["Overlay shape<br/>boundary-to-boundary shortcuts"]:::service
SPD@{ icon: "tech:redis", label: "Live speeds<br/>per segment" }
CUST["Customize, every few seconds<br/>shortcut costs, bottom up, in parallel"]:::core
Q["Query<br/>real roads in the two end cells,<br/>shortcuts everywhere between"]:::hot
OUT(["Route in ~2 ms<br/>a few thousand junctions scanned"]):::ok
MAP -.->|"when the map changes"| PART --> TOPO
TOPO --> CUST
SPD ==> CUST
CUST ==> Q --> OUT
class MAP,SPD store- Cells at several levels: e.g. neighbourhood-sized cells inside city-sized cells inside state-sized cells. A query uses the finest level only near its two ends and the coarsest in the middle, so a 1,400 km route crosses India mostly on a few hundred shortcuts.
- Good cells have few boundary roads. Rivers, hills and railways do that naturally; the partitioner (PUNCH, in the CRP papers) finds cuts that cross as few roads as possible, which keeps the overlay small.
- A closure is a cost, not a topology change. Setting a segment's cost to infinity and re-customizing its cell takes milliseconds; editing the graph waits for the slow loop.
- Each cost function is just another set of shortcut costs, 0.07 GiB per function on the Western Europe graph, so "avoid tolls" doesn't need a second routing system.
# routing/crp_customize.py
"""
CRP customization for one cell (illustrative). The cell's boundary junctions and
its internal roads never change between map versions; only the costs do. For each
boundary junction, a Dijkstra restricted to the cell gives the cost of the
shortcut to every other boundary junction. Cells are independent, so they run in
parallel; higher levels reuse the shortcut costs of the level below.
"""
import heapq
import math
CLOSED = math.inf
def segment_cost(seg_id: int, length_m: float, speeds: dict, closures: set, default_kmh: float) -> float:
if seg_id in closures:
return CLOSED
kmh = speeds.get(seg_id, default_kmh)
return length_m / (kmh / 3.6) # seconds
def customize_cell(cell, speeds: dict, closures: set) -> dict:
"""Returns {(from_boundary, to_boundary): seconds} for one cell."""
costs = {}
for src in cell.boundary:
dist = {src: 0.0}
heap = [(0.0, src)]
while heap:
d, u = heapq.heappop(heap)
if d > dist.get(u, math.inf):
continue
for seg in cell.out_segments(u): # only roads inside this cell
c = segment_cost(seg.id, seg.length_m, speeds, closures, seg.default_kmh)
nd = d + c + cell.turn_cost(u, seg)
if nd < dist.get(seg.to, math.inf):
dist[seg.to] = nd
heapq.heappush(heap, (nd, seg.to))
for dst in cell.boundary:
if dst != src:
costs[(src, dst)] = dist.get(dst, CLOSED)
return costsWhere the graph lives#
One region, one server
The world's road graph is ~1 billion segments (estimate). You need 100,000 queries a second at peak. Do you shard one graph across many machines, so each query crosses the network many times, or something else? What about a route that leaves the region?
One strong answer
- Replicate whole regions, don't shard a query. CRP's data for Western Europe is about 3 GiB plus 70 MB per cost function: a region that size fits in one server's memory. Every query runs on one machine with no network hops inside the search.
- Partition by geography: ~25 regions of that size, each served by a pool of identical replicas; a query goes to the pool of its origin's region. Peak load spreads with the clock, since rush hour moves around the world.
- Regions overlap at their borders, so a trip that crosses a border by a few kilometres stays inside one region's graph.
- Long trips across regions use a global server holding only the coarsest overlay level for the world, plus the full detail of the two end cells fetched from the regional servers.
- Customization runs once per region and is broadcast to that region's replicas: the costs, not the graph, change every few seconds.
# routing/regions.py
"""
Routing by region (illustrative). Each pool holds one region's whole graph and
overlay in memory; a query never crosses the network inside its search. Cross-
region trips go to the global tier, which keeps only the top overlay level.
"""
from dataclasses import dataclass
@dataclass
class Region:
name: str
bbox: tuple # (min_lat, min_lng, max_lat, max_lng), overlapping neighbours
pool: str # e.g. "route-south-asia"
def pick_pool(origin: tuple, dest: tuple, regions: list) -> str:
def inside(p, r):
return r.bbox[0] <= p[0] <= r.bbox[2] and r.bbox[1] <= p[1] <= r.bbox[3]
for r in regions:
if inside(origin, r) and inside(dest, r):
return r.pool # the common case: both ends in one region
return "route-global" # top-level overlay plus the two end cellsDeep Dive 2: Live Traffic From Phone Probes (Week 8 Concepts: Event Ingestion, Late Data; Week 9: Privacy)#
"Routing is only as good as its costs. The costs come from phones on the road: noisy, sparse, sometimes late, sometimes not even in a car. The pipeline turns them into one trustworthy speed per road segment, within about two minutes, without being able to follow any one phone."
This builds on Week 8, Day 1 · Event Ingestion and Week 8, Day 4 · Late Data.
Which road, and how fast?
A phone reports a location every 5 seconds, accurate to maybe 10 to 30 metres. Near the Hebbal flyover, one fix is equally close to the flyover and to the service road beneath it. How do you decide which road the phone is on? Then: a bus waits at a stop, a delivery rider parks for five minutes, someone walks along the road with the app open. How do you keep them from painting the road red?
One strong answer
- Map matching as a hidden Markov model (Newson and Krumm, 2009): the hidden state is the road segment, the observation is the noisy fix. A fix is likely on segments near it (emission), and consecutive fixes are likely on segments where the driving distance between them is close to the straight-line distance (transition). The Viterbi algorithm finds the most likely sequence of segments.
- The flyover is decided by the sequence, not the single fix: a phone that came off the ramp can't be on the service road without an impossible detour.
- Output traversals, not points: "a device crossed segment 81233 in 41 s". Speed is length divided by time over the whole segment, which averages out GPS jitter.
- Filter what isn't traffic: walking speeds on a highway, a device stationary for minutes off the carriageway, a traversal that implies 300 km/h.
- Aggregate across devices: a segment's live speed is the median of recent traversals from at least a few distinct devices. One bus at a stop is one device; a jam is many.
%% title: From phone fixes to segment speeds
flowchart TD
PH@{ icon: "sd:phone", label: "Phones navigating<br/>a fix every 5 s, sent every 30 s" }
GW@{ icon: "sd:api", label: "Probe endpoint<br/>anonymous session id" }
KP@{ icon: "tech:kafka", label: "Kafka · probes<br/>partitioned by region" }
MM["Map matching<br/>HMM, Viterbi over segments"]:::core
TRIM["Privacy trim<br/>drop trip start and end"]:::bad
AGG["Segment speeds<br/>1-min windows, median, min devices"]:::hot
SPD@{ icon: "tech:redis", label: "Redis · live speeds<br/>speed, devices, age" }
HIST@{ icon: "sd:database", label: "Historical profiles<br/>rebuilt nightly" }
CUST["Customizer<br/>and traffic overlay"]:::service
PH ==> GW ==> KP ==> MM --> TRIM --> AGG ==> SPD --> CUST
KP -.->|"nightly batch"| HIST
HIST -.->|"where live is thin"| CUST
class KP queue
class SPD,HIST store- Partition the stream by region, so map matching for Bengaluru runs on workers that hold Bengaluru's road graph in memory. A phone's batches carry a short-lived session id, so its fixes land on one worker and can be matched as a sequence.
- Late batches are normal: a phone in an underpass sends a minute of fixes at once. Matching uses event time; the 1-minute window waits a few seconds for stragglers and then closes. A traversal that arrives after its window updates the next one rather than being dropped.
- Privacy is built into the pipeline, following what Google described in 2009: anonymous ids that rotate, reports combined across devices in busy areas, and the start and end of every trip deleted before anything is stored.
- The freshness budget: up to 30 s in the phone's batch + ~10 s to match + a 60 s window + one customization round of under a second ≈ under 2 minutes from cars slowing to routes avoiding them.
# traffic/map_match.py
"""
HMM map matching in the style of Newson and Krumm (2009), much simplified and
illustrative. Candidates are segments near each fix; emission favours segments
close to the fix; transition favours paths whose driving distance matches the
straight-line distance between fixes. Viterbi keeps the best path so far.
"""
import math
SIGMA_M = 15.0 # GPS noise
BETA_M = 50.0 # tolerance for route-vs-straight-line mismatch
def emission(dist_to_segment_m: float) -> float:
return -0.5 * (dist_to_segment_m / SIGMA_M) ** 2 # log-probability
def transition(route_m: float, straight_m: float) -> float:
return -abs(route_m - straight_m) / BETA_M
def match(fixes: list, graph) -> list:
"""fixes: [(lat, lng, t)]. Returns the most likely segment for each fix."""
layers = [graph.candidates(f, radius_m=50) for f in fixes] # [(seg, dist_m)]
score = {seg: emission(d) for seg, d in layers[0]}
back = [{}]
for i in range(1, len(fixes)):
straight = graph.haversine_m(fixes[i - 1], fixes[i])
new, ptr = {}, {}
for seg, d in layers[i]:
best, arg = -math.inf, None
for prev, s in score.items():
route = graph.driving_distance_m(prev, seg, limit_m=straight * 3)
if route is None:
continue # impossible move
v = s + transition(route, straight)
if v > best:
best, arg = v, prev
if arg is not None:
new[seg], ptr[seg] = best + emission(d), arg
score, back = new, back + [ptr]
seg = max(score, key=score.get)
path = [seg]
for ptr in reversed(back[1:]):
seg = ptr[seg]
path.append(seg)
return list(reversed(path))# traffic/segment_speed.py
"""
One speed per segment (illustrative). Live traversals in the last few minutes are
trusted only with enough distinct devices; otherwise the historical profile for
this weekday and quarter-hour fills in. Live readings fade toward history as
they age, so a road that empties out doesn't stay red.
"""
import math
from statistics import median
MIN_DEVICES = 3
WINDOW_S = 300
FADE_S = 240
def live_speed(traversals: list, now: float):
recent = [t for t in traversals if now - t["at"] <= WINDOW_S and 2 <= t["kmh"] <= 160]
devices = {t["device"] for t in recent}
if len(devices) < MIN_DEVICES:
return None, 0, None
newest = max(t["at"] for t in recent)
return median(t["kmh"] for t in recent), len(devices), now - newest
def segment_speed(seg_id: int, traversals: list, history, now: float, weekday: int, quarter: int) -> float:
hist = history.kmh(seg_id, weekday, quarter)
kmh, n, age = live_speed(traversals, now)
if kmh is None:
return hist
trust = (n / (n + 5)) * math.exp(-age / FADE_S) # more devices, fresher: more trust
return trust * kmh + (1 - trust) * histRouting and traffic
Q1/4 Why is CRP a better fit than contraction hierarchies for routing on live traffic?
Answer & explanation
Answer: B
CH queries are actually faster (0.11 ms vs 1.65 ms), but CRP's customization took 0.37 s on 12 cores for Western Europe, against minutes to contract again.
Q2/4 A road is closed for a marathon. What is the fastest way to make routing avoid it?
Answer & explanation
Answer: B
A closure is just a cost. Customizing one cell takes milliseconds; the map data and tiles can catch up later.
Q3/4 One fix is equally close to a flyover and the service road beneath it. How does map matching choose?
Answer & explanation
Answer: C
The hidden Markov model scores whole paths; an impossible detour rules out the wrong road.
Q4/4 A single bus waits at a stop for three minutes. Why doesn't the road turn red?
Answer & explanation
Answer: B
One stationary device is not a jam. A minimum count of devices is both a quality rule and a privacy rule.
Deep Dive 3: Predicting the Arrival Time (Week 8 Concepts: Streaming vs Batch; Week 7: Ranking Models)#
"A route is a list of segments. The naive ETA adds up each segment's current travel time. But Ananya reaches Hebbal 30 minutes from now, and the speed there now is not the speed there then."
This builds on Week 8, Day 2 · Streaming vs Batch.
The jam that hasn't started yet
At 5:40 pm, the Hebbal flyover is flowing at 35 km/h. Ananya will reach it at about 6:15. On weekdays at 6:15 the historical profile says it crawls at 10 km/h. Which speed should her ETA use for that stretch, and which should it use for the road outside her flat? What else, besides segment speeds, adds time? And how do you know the ETA is any good?
One strong answer
- Use the speed expected when she gets there. Walk the route in order: at each segment, look up the speed for the time she'll arrive, then add that segment's time before moving on. Near segments use live speeds; far ones lean on the historical profile for that weekday and quarter-hour.
- Blend by horizon: for the next ~10 minutes trust live speeds; by 30 minutes out trust history; in between, mix. Hebbal at 6:15 is mostly history: 10 km/h.
- Junctions and turns cost time too: a right turn across traffic, a signal, a toll plaza. These come from probes as well: the time between leaving one segment and entering the next.
- A learned correction: a model reads the live and historical speeds of a stretch of connected roads and predicts how a slowdown will spread. DeepMind's graph neural networks over "Supersegments" improved real-time ETA accuracy by up to 50% in some cities.
- Measure it: every finished trip is a label. Track the share of trips that arrive within a tolerance of the first ETA; Google reported over 97%.
What Google and DeepMind have published: Google's 2020 post says Maps analyses "historical traffic patterns for roads over time" and combines them with live traffic conditions, then uses machine learning to forecast. During 2020's lockdowns, with up to a 50% drop in worldwide traffic, it began "automatically prioritizing historical traffic patterns from the last two to four weeks, and deprioritizing patterns from any time before that". DeepMind's post describes Supersegments, "multiple adjacent segments of road that share significant traffic volume", a graph neural network trained on them, and the training difficulty: batches ranged from two-node graphs to graphs of more than 100 nodes, which DeepMind handled with MetaGradients to adapt the learning rate and a combination of several loss functions.
%% title: Timing one route
flowchart TD
RT(["Route<br/>412 segments, 38 km"]):::client
WALK["Walk the route in order<br/>clock starts at departure"]:::core
LIVE@{ icon: "tech:redis", label: "Live speeds<br/>next ~10 minutes" }
HIST@{ icon: "sd:database", label: "Historical profile<br/>weekday, quarter-hour" }
TURN["Junction and turn delays<br/>signals, tolls, crossings"]:::service
GNN@{ icon: "sd:ml", label: "Correction model<br/>per Supersegment" }
OUT(["ETA 1 hr 12 min<br/>and a likely range"]):::ok
RT --> WALK
LIVE -.-> WALK
HIST -.-> WALK
TURN -.-> WALK
WALK --> GNN --> OUT
class LIVE,HIST store- Route choice and ETA use the same speeds where they can. The routing query runs on the current customization (live speeds plus history where live is thin); the ETA service then times the two or three alternatives with the time-dependent walk and orders them by ETA. A route that looks fastest now but meets the 6:15 Hebbal jam can drop to second.
- "Leave at 6 pm" is a separate customization built from historical profiles for that weekday and time, refreshed every 15 minutes; the query and the walk are the same.
- A range, not a point: the same model can predict the spread of past trips like this one. Showing "1 hr 10 to 1 hr 20" when the road is unpredictable is more honest than one number.
- When the model is down, the walk alone still gives a sensible ETA. The model corrects; it never replaces the sum.
# eta/predict.py
"""
A time-dependent ETA (illustrative). Each segment's speed is the one expected
when the driver reaches it: live for the near future, history further out, a
blend in between. A learned correction per Supersegment adjusts the sum.
"""
NEAR_S = 600 # trust live speeds for the next 10 minutes
FAR_S = 1800 # trust history from 30 minutes out
def blend(live_kmh, hist_kmh: float, ahead_s: float) -> float:
if live_kmh is None or ahead_s >= FAR_S:
return hist_kmh
if ahead_s <= NEAR_S:
return live_kmh
w = (FAR_S - ahead_s) / (FAR_S - NEAR_S)
return w * live_kmh + (1 - w) * hist_kmh
def eta_seconds(route: list, depart_at: float, live, history, turns, model) -> float:
t = depart_at
for prev, seg in zip([None] + route[:-1], route):
ahead = t - depart_at
kmh = blend(live.kmh(seg.id), history.kmh_at(seg.id, t), ahead)
t += seg.length_m / max(kmh, 3.0) * 3.6 + turns.delay_s(prev, seg)
raw = t - depart_at
return raw * model.correction(route, depart_at) # ~1.0; falls back to 1.0 if unavailableDeep Dive 4: Vector Tiles, Quadkeys and Caching (Week 4 Concepts: Multi-Tier Caching, Cache Invalidation)#
"The map on screen is a mosaic of square tiles. Every viewer of central Bengaluru at a given zoom wants the same tiles, so this is a cache problem. What makes it interesting is naming them, keeping them small, and changing them when a road changes."
This builds on Week 4, Day 5 · Multi-Tier Caching and Week 4, Day 2 · Cache Invalidation.
A world of tiles
Google wrote in 2010 that covering the world at 20 zoom levels takes more than 360 billion image tiles. How do you name a tile so that neighbours and parents are easy to find? What do you put in a tile so that there are far fewer of them? When a new flyover opens, which tiles change, and how do you make sure no CDN edge keeps serving the old ones?
One strong answer
- One grid for the world: project to Web Mercator (latitudes clipped at about ±85.05°), cut each zoom level z into 2^z × 2^z tiles of 256 pixels. Microsoft's documented Bing Maps tile system names each tile with a quadkey: interleave the bits of y and x, one base-4 digit per level. Tile (3, 5) at level 3 is "213".
- Quadkeys nest: a tile's quadkey starts with its parent's, so "2130" to "2133" are the four children of "213". Prefixes are areas: an offline download of a city is every tile under a few prefixes, and nearby tiles usually sort near each other in storage.
- Vector, not raster: a vector tile holds the geometry (roads, buildings, labels) and the phone draws it. Each vector tile works across several zoom levels, which Google said needs "more than 100 times less data", and lets the map rotate, tilt and zoom smoothly.
- A new flyover dirties one tile per level: its zoom-16 tile and that tile's 16 ancestors, all found by truncating one quadkey.
- Never overwrite a tile in place: tile URLs carry the map version, and a new version is a new set of URLs. Unchanged tiles keep their old content but are served under the new version by copy-on-write in storage, so nothing cached is ever stale.
%% title: From a map edit to a tile on screen
flowchart TD
ED(["Map edits<br/>100 million a day, most are places"]):::client
MAPDB@{ icon: "sd:database", label: "Map data<br/>versioned" }
DIRTY["Dirty quadkeys<br/>edit tile + 16 ancestors, deduplicated"]:::core
TB["Tile builder<br/>rebuild dirty tiles only"]:::service
TS@{ icon: "sd:object-storage", label: "Tile store<br/>v1843/2130231… immutable" }
EDGE@{ icon: "sd:cdn", label: "CDN edges<br/>95%+ hits" }
PH@{ icon: "sd:phone", label: "Phone<br/>draws vectors, caches areas" }
TRF["Traffic overlay<br/>per tile, expires in ~60 s"]:::hot
ED --> MAPDB --> DIRTY --> TB --> TS --> EDGE --> PH
TRF -.-> EDGE
class MAPDB,TS store
class EDGE edge- Most of the 100 million daily updates don't touch geometry: opening hours, a new café, a phone number. Those are place data fetched with the place, not baked into tiles. Geometry changes are batched into a new map version every few minutes to hours.
- The phone keeps a version pointer: after an update it asks for the new version's manifest and fetches only tiles whose content changed; the rest are already in its cache under the same content hash.
- Traffic stays out of base tiles: the overlay holds a colour per visible segment for one tile, expires in about a minute, and is tiny. The base tile can be cached for days.
- Popular tiles are tiny in number: a city centre at every zoom is a few thousand tiles. CDN edges keep them hot, and a major event that brings a crowd to one stadium is still one set of tiles.
# tiles/quadkey.py
"""
Tile names in the scheme Microsoft documents for Bing Maps (illustrative Python).
Web Mercator, 256-pixel tiles, one base-4 digit per level: the quadkey of a tile
starts with the quadkey of its parent.
"""
import math
MAX_LAT = 85.05112878
def tile_xy(lat: float, lng: float, level: int) -> tuple:
lat = max(-MAX_LAT, min(MAX_LAT, lat))
s = math.sin(math.radians(lat))
x = (lng + 180) / 360
y = 0.5 - math.log((1 + s) / (1 - s)) / (4 * math.pi)
n = 1 << level
return min(n - 1, int(x * n)), min(n - 1, int(y * n))
def quadkey(tx: int, ty: int, level: int) -> str:
digits = []
for i in range(level, 0, -1):
mask = 1 << (i - 1)
d = (1 if tx & mask else 0) + (2 if ty & mask else 0)
digits.append(str(d))
return "".join(digits) # quadkey(3, 5, 3) == "213"
def dirty_tiles(lat: float, lng: float, max_level: int = 16) -> list:
"""An edit at one point dirties its deepest tile and every ancestor."""
qk = quadkey(*tile_xy(lat, lng, max_level), max_level)
return [qk[:k] for k in range(1, max_level + 1)]
def tile_url(version: int, qk: str) -> str:
return f"https://tiles.example.com/v{version}/{qk}.mvt" # immutable: cache foreverETAs and tiles
Q1/4 Ananya will reach the Hebbal flyover in 35 minutes. Which speed should her ETA use there?
Answer & explanation
Answer: B
The time-dependent walk uses the speed expected at arrival: live for the near future, history further out.
Q2/4 What did DeepMind's graph neural networks add to Maps' ETAs?
Answer & explanation
Answer: B
The model reads Supersegments of connected roads and corrects the ETA; historical and live speeds remain its inputs.
Q3/4 The quadkey of a tile is "2130". What is the quadkey of its parent?
Answer & explanation
Answer: B
One digit per level: drop the last digit to go up a level. That's how one edit's 16 ancestors are found.
Q4/4 Why put the map version in every tile URL?
Answer & explanation
Answer: C
A new version is new URLs; old cached tiles simply stop being asked for.
Phase 5: Scaling and Edge Cases (5 minutes)#
"Walk me through Ananya's evening, and then the cases that bite."
Ananya's Evening#
- 5:40 PM · Directions
- Three alternatives from the Bengaluru routing pool in under 200 ms; the walk times Hebbal at its 6:15 speed: 1 hr 12 min
- 5:45 PM · She leaves
- A fix every 5 s, a batch every 30 s; a route check every minute
- 6:02 PM · Truck breaks down on the Outer Ring Road
- Phones behind it slow to 5 km/h; their traversals land in the next 1-minute window
- 6:04 PM · Routes avoid it
- 20 devices agree, the segment's speed drops, its cell is re-customized; drivers on that route get a faster one
- 6:57 PM · She arrives
- The ETA was within 3 minutes; the first and last stretch of her trip are deleted from the probe data
Edge Cases#
Edge case 1: Everyone is sent down the same side street
Problem: The ORR slows and the router sends 20,000 drivers to the same narrow alternative, which jams within minutes.
Solution: Feed the router's own decisions back: count how many active routes are about to enter each segment and add that expected load to the cost when it nears the road's capacity. Spread drivers across near-equal alternatives instead of always picking the single best.
Edge case 2: A road with almost no probes
Problem: At 3 am, or on a rural road, a single phone crossed a segment at 90 km/h an hour ago.
Solution: Below the minimum count of devices, show no live colour and use the historical profile, or a default by road class where there is no history. Live speeds fade back to history as they age.
Edge case 3: Fake traffic
Problem: In 2020 an artist in Berlin walked 99 phones in a handcart down streets and made them show as jammed.
Solution: Treat probes as untrusted input: many devices moving in lockstep at walking pace, devices with no history, or devices that never leave one street get less weight. Cross-check a "jam" against other signals (flow on neighbouring segments, reports) before rerouting many drivers around it.
Edge case 4: Closed or jammed?
Problem: A flooded underpass and a bad jam both show no fast traversals.
Solution: A jam still has slow traversals and cars leaving it; a closure has none where history expects hundreds, plus U-turns at its entry and driver reports. Mark it closed (cost infinity) after a short confirmation, and reopen as soon as traversals reappear.
Scaling and edge cases
Q1/3 The router sends 20,000 drivers onto the same side street, which jams. What prevents it?
Answer & explanation
Answer: C
The router's own decisions are traffic that hasn't happened yet; counting them avoids creating the next jam.
Q2/3 A single phone crossed a rural road at 90 km/h an hour ago. What speed should routing use?
Answer & explanation
Answer: A
Too few devices and too old: the segment falls back to history.
Q3/3 How do you tell a closed road from a jam using probes?
Answer & explanation
Answer: C
The absence of traversals, against history, is the signal; confirm, then set the cost to infinity.
Phase 6: Monitoring and Operations#
"The dashboard asks four questions: are probes arriving and matching, are speeds fresh, is routing fast and right, and are tiles coming from the edge?"
Monitoring Dashboard#
Probes and traffic
| Metric | Now |
|---|---|
| Probe points in | 571,000/s |
| Fixes matched to a segment | 97.8% |
| Consumer lag, slowest region | 6 s |
| Segments with live speeds (≥ 3 devices, < 5 min) | 41 million |
Routing
- Route queries: 58,400/s; p50 / p99 38 / 212 ms
- Overlay costs age, worst region: 4.1 s; customization time p99: 0.6 s
ETA
- Trips within the arrival tolerance, last hour: 97.3%
- Correction model available: 99.97%
Tiles
- Tile fetches 930,000/s; CDN hit rate 96.1%; traffic overlay age p99 74 s
SLOs for the Platform#
SLO 1: Fast directions
- Target: directions p99 < 300 ms; 99.99% of requests answered, falling back to history if live speeds are unavailable
SLO 2: Fresh traffic
- Target: 99% of regions with overlay costs younger than 30 s; a jam reflected in routing within 2 minutes
SLO 3: Honest ETAs
- Target: 97% of trips arrive within the tolerance of the first ETA, measured daily per city
SLO 4: The map always loads
- Target: tile p99 < 100 ms from the edge; no tile served from a map version older than the phone's
Reading the dashboard
Q1/3 The overlay costs in one region are 9 minutes old. What do drivers there notice?
Answer & explanation
Answer: A
Routing still answers from stale costs; it just doesn't see new jams. That's why cost age has its own SLO.
Q2/3 The share of fixes matched to a segment falls from 97.8% to 70% in one city. What is the likeliest cause?
Answer & explanation
Answer: B
Phones driving where the graph has no road can't be matched; a drop in one city points at map data.
Q3/3 Trips within tolerance drop from 97% to 91% in one city, with probes and routing healthy. Where do you look first?
Answer & explanation
Answer: C
If live data is fine, the predictions for later on the trip are wrong: history has stopped describing the city, as in 2020.
Final Challenge: The First Monsoon Evening in Mumbai#
The interviewer looks out of the window. "One more."
"Mumbai, the first heavy monsoon evening. Between 6 and 7 pm, about 120 road segments flood: underpasses, low junctions. Twice as many people as usual open the map, everyone already driving keeps asking for a way out, and probes show long stretches at walking pace. Keep it working and tell me what you'd degrade."
- Phones navigating in the Mumbai region at 6 pm: 300,000 (assumption)
- New direction requests: normally 1,000 a second, tonight 2×
- Route checks: once a minute per navigating phone
- Reroutes: 40,000 drivers whose route crosses a flooded segment, within 2 minutes of each closure
- Each route query costs ~10 ms of CPU (estimate from the routing deep dive)
What do you protect, and what do you degrade?
Work out the probe rate, the route queries a second and the CPU, then decide how flooded roads get closed, how you avoid sending everyone to the same dry road, and what to slow down.
One strong answer
- Probes: 300,000 ÷ 5 s = 60,000 points a second, ordinary for the pipeline; the region's partitions already absorb it.
- Route queries: 2,000 new + 300,000 ÷ 60 = 5,000 checks + 40,000 ÷ 120 s ≈ 330 reroutes = ~7,300 a second; at 10 ms each, ~73 cores. CPU isn't the problem.
- Closures: a flooded underpass shows no traversals where history expects hundreds, U-turns at its entry and driver reports. Confirm quickly and set its cost to infinity; customizing those cells takes milliseconds. Never wait for the map data's slow loop.
- Herding: 40,000 rerouted drivers on the same two dry roads would flood them with cars instead. Add the expected load of assigned routes to costs and spread drivers across alternatives.
- ETAs: history has never seen this evening. Lean on live speeds further out than usual, widen the shown range, and say "delays likely".
- Degrade in this order: alternatives per request (3 down to 1), the correction model (the plain time-dependent walk remains), the traffic overlay refresh (60 s to 3 min), route checks for drivers far from any flooding (1 to 3 minutes). Never closures being applied, turn-by-turn guidance, or tiles and routes already on the phone.
Final challenge · Quest check
Q1/4 300,000 phones check for a better route once a minute. How many route checks a second?
Answer & explanation
Answer: B
300,000 ÷ 60 = 5,000 a second, plus 2,000 new requests and about 330 reroutes.
Q2/4 About 7,300 route queries a second at ~10 ms of CPU each. Roughly how many cores?
Answer & explanation
Answer: B
7,300 × 0.01 s = 73 core-seconds every second. Preprocessing makes the spike cheap; correctness is the hard part.
Q3/4 A flooded underpass has no traversals, several U-turns and five driver reports. What does the system do?
Answer & explanation
Answer: C
Closures are costs. The tiles and the map data can follow once the water goes down.
Q4/4 Which should be degraded first?
Answer & explanation
Answer: A
Alternatives are a nicety; closures and guidance keep people out of the water.
Interview Conclusion#
"Why not just run Dijkstra on a big enough cluster?"
"Because on a continental graph it scans millions of junctions per query: 2.2 seconds on the Western Europe benchmark. At 100,000 queries a second that's a data centre of cores for something preprocessing does in a couple of milliseconds."
"And why not the fastest preprocessing, contraction hierarchies?"
"Its queries are faster, but its shortcuts bake in the speeds, and our speeds change every minute. CRP splits the work: the partition and overlay shape change only when the map does, and the costs are recomputed from live traffic in under a second."
"The single most important decision?"
"Separate what depends on the map from what depends on traffic. The graph, its partition and the tiles change slowly and are cached everywhere; the speeds change every minute and flow through a small, fresh table that routing, ETAs and the traffic overlay all read."
Summary: Concepts Applied from 10-Week Course#
- Week 1: Data at scale
- Partitioning — the road graph into cells with few boundary roads; routing pools by region
- Hot keys — city-centre tiles; one region's rush hour
- Week 3: Messaging and async processing
- Probe batches through a log partitioned by region
- Week 4: Caching
- Multi-tier caching — phone, CDN edge, tile store; cache invalidation — versioned, immutable tile URLs
- Week 7: Search systems
- Precomputed indexes trading preprocessing for query time, as in an inverted index
- Week 8: Analytics pipelines
- Event ingestion, event-time windows and late data for probes; batch-built historical profiles
- Week 9: Multi-tenancy and security
- Anonymous probes, minimum device counts, trip ends deleted; untrusted input
Why Google Maps Matters#
The answer depends on data that changes while you compute it
- A route is the shortest path on a graph whose weights change every minute, so the preprocessing has to be split by what it depends on
The sensors are the users
- Traffic comes from phones that are noisy, sparse, late and sometimes not in a car; turning them into a speed is a streaming problem with privacy built in
The question is about the future
- An ETA asks how fast a road will be when you reach it, not how fast it is now
"Precompute what depends on the map, recompute what depends on traffic, and time every road for when you'll get there."
Self-Assessment Checklist#
After studying this case study, you should be able to:
Routing:
- Explain why Dijkstra doesn't scale to continental graphs at 100,000 queries a second
- Describe contraction hierarchies and why traffic forces them to contract again
- Describe CRP's partition, overlay and customization, and why it suits live traffic
- Place routing data by region and handle trips across regions
Traffic and ETAs:
- Map-match noisy fixes with a hidden Markov model and turn them into segment speeds
- Blend live and historical speeds by device count and age, with privacy rules
- Compute a time-dependent ETA and explain what a learned correction adds
Tiles and operations:
- Name tiles with quadkeys, find dirty ancestors, and version tile URLs
- Plan a monsoon evening: closures as costs, herding, what to degrade
Sources#
Google and DeepMind:
- Blog
Miriam Daniel — Google Maps celebrates 20 years with 20 favorite features (more than 2 billion users a month; 1 trillion km of directions in 2024; 250 million places; 500 million contributors; 100 million updates a day), 6 February 2025: https://blog.google/products-and-platforms/products/maps/20-years-google-maps-20-features/
- Blog
Johann Lau — Google Maps 101: How AI helps predict traffic and determine routes (1 billion km driven a day, 220+ countries and territories, ETAs accurate for over 97% of trips, aggregate location data, historical patterns, last two to four weeks prioritised, up to 50% drop in traffic, route factors), 3 September 2020: https://blog.google/products/maps/google-maps-101-how-ai-helps-predict-traffic-and-determine-routes/
- Blog
Oliver Lange, Luis Perez — Traffic prediction with advanced Graph Neural Networks (Supersegments, up to 50% better real-time ETAs, 51% in Taichung, MetaGradients, combined losses), DeepMind, 3 September 2020: https://deepmind.google/blog/traffic-prediction-with-advanced-graph-neural-networks/
- Blog
Dave Barth — The bright side of sitting in traffic: Crowdsourcing road congestion data (anonymous speed and location, combining reports, deleting trip start and end points), August 2009: https://googleblog.blogspot.com/2009/08/bright-side-of-sitting-in-traffic.html
- Blog
Michael Siliski — The next generation of mobile maps (vector graphics, 100 times less data across zoom levels, almost 70% less mobile data), 16 December 2010: https://maps.googleblog.com/2010/12/next-generation-of-mobile-maps.html
- Blog
Andrew Miller — Under the hood of Google Maps 5.0 for Android (more than 360 billion tiles at 20 zoom levels; vector tiles as blueprints; each works across several zoom levels), 17 December 2010: https://maps.googleblog.com/2010/12/under-hood-of-google-maps-50-for.html
Research and documentation:
- Paper
Hannah Bast, Daniel Delling, Andrew Goldberg, Matthias Müller-Hannemann, Thomas Pajor, Peter Sanders, Dorothea Wagner, Renato Werneck — Route Planning in Transportation Networks (Western Europe: 18.0 million junctions, 42.5 million arcs; Dijkstra 2.2 s; CH 0.11 ms; CRP 1.65 ms, customization 0.37 s on 12 cores, 36 ms with GPUs; Bing Maps uses CRP, OSRM uses CH), 2015: https://arxiv.org/abs/1504.05140
- Paper
Paul Newson, John Krumm — Hidden Markov Map Matching Through Noise and Sparseness, ACM SIGSPATIAL GIS 2009: https://www.microsoft.com/en-us/research/publication/hidden-markov-map-matching-noise-sparseness/
- Blog
Joe Schwartz — Bing Maps Tile System (Mercator, 256-pixel tiles, levels 1 to 23, latitude clipped at 85.05112878, quadkeys), Microsoft Learn: https://learn.microsoft.com/en-us/bingmaps/articles/bing-maps-tile-system
Reporting:
- Blog
CNN — Artist uses 99 phones to trick Google into traffic jam alert (Simon Weckert, Berlin), February 2020: https://amp.cnn.com/cnn/style/article/artist-google-traffic-jam-alert-trick-scli-intl
Design note: the architecture, code, thresholds, rates, sizes and dashboards here are an illustrative design for the problem, not a description of Google's internal systems beyond what Google and DeepMind have published. Estimates in Phase 2 are labelled as assumptions or estimates.
Further Reading#
Related systems to study:
- Read
Uber (Bonus Problem 9 in this series): H3 cells, driver location pings and ETAs for matching
- Read
Swiggy (Bonus Problem 17): rider location ingest and the ETA as a calibrated distribution
- Read
Blinkit (Bonus Problem 12): store density, road distance and promise times
- Book
Google Search (Bonus Problem 5): another system that wins by precomputing an index
Papers:
- Read
Robert Geisberger, Peter Sanders, Dominik Schultes, Daniel Delling — Contraction Hierarchies: Faster and Simpler Hierarchical Routing in Road Networks (WEA 2008)
- Read
Daniel Delling, Andrew Goldberg, Thomas Pajor, Renato Werneck — Customizable Route Planning (SEA 2011) and Customizable Route Planning in Road Networks (Transportation Science, 2017)
Foundational concepts (this course):
- Read
Week 1, Day 1 — Partitioning Deep-Dive
- Read
Week 4, Day 2 — Cache Invalidation
- Read
Week 4, Day 5 — Multi-Tier Caching
- Read
Week 8, Day 1 — Event Ingestion
- Read
Week 8, Day 2 — Streaming vs Batch
- Read
Week 8, Day 4 — Late Data
"The map changes a hundred million times a day and the roads change speed every minute; precompute the first, stream the second, and time every road for when you'll reach it."
- 0/7stages cleared
New lessons by email
New lessons, side quests and case files as they ship, at most once a week. No spam, unsubscribe anytime.
Discussion · …
Public: everyone reading this lesson sees comments. Be kind and keep it on the material.