Hiring BarSupport

Design Google Maps (Geospatial Indexing) — Staff-Level Case Study

Case study62 min read7 diagrams

Technologies referenced in this case study: Redis · PostgreSQL · Kafka · Flink · Cassandra · Elasticsearch

Related case studies: Proximity Matching · Logistics & Delivery · CDN & Edge Caching · Stream Processing · Search Indexing

Related patterns: Scaling Reads · Data Pipelines · Degraded Mode · Database Indexing · Sharding

How to Use This Case Study#

Organized for interview use first, reference second.

ModeTimeWhat to Read
Quick Review15 minExecutive Summary → Interview Walkthrough → Fault Lines table → Drills 1–3
Targeted Study1–2 hrsExecutive Summary → Walkthrough → Sections 3–4 → Deep Dives on traffic and routing
Deep Dive3+ hrsEverything, including the Principal Lens (Section 11) and Appendices
What is Google Maps, as a system? — Why interviewers pick this topic

"Design Google Maps" is really four systems sharing one coordinate space: map tiles (render and serve the picture), place search (find "coffee near me"), routing (shortest path on a road graph with hundreds of millions of edges), and ETA + live traffic (turn billions of phone GPS pings into per-road-segment speeds within minutes). Each has a different bottleneck: tiles are a CDN problem, search is an index problem, routing is a precomputation problem, traffic is a streaming problem.

Before vs After — the "stale traffic" incident:

Without live traffic in the routing cost function:
t=0:      Multi-car crash closes 3 of 4 lanes on a highway at 5:10pm
t=+2min:  Road speed on the segment drops from 100 km/h to 12 km/h
t=+2min:  Router still uses the historical Tuesday-5pm profile: 65 km/h
t=+10min: 40,000 drivers routed onto the jammed segment; ETAs off by 25+ min
t=+30min: Social media: "Maps drove me straight into the jam." Trust damage.

With a streaming traffic pipeline feeding routing weights:
t=0:      Same crash
t=+45s:   Probes from ~60 phones on the segment map-matched; speed estimate 15 km/h
t=+90s:   Segment weight published to routing (1-min window, min 5 probes)
t=+2min:  New queries avoid the segment; in-progress navigations get a reroute offer
t=+5min:  ETA error back within ±10%; the jam is shorter because we stopped feeding it

Why interviewers reach for this question: It tests whether you can decompose a giant, vague product into subsystems with different read/write/freshness profiles, choose a spatial index for a stated query pattern, and reason about precompute-vs-freshness in the routing engine. Every one of those is a judgment call with a victim.

Mechanics Refresher: Spatial Indexes and Routing Algorithms
Spatial IndexHow It WorksProsCons
GeohashInterleave lat/lng bits, base32-encode; prefix = containing cellString prefix queries in any KV/B-tree; trivial to shardRectangular cells distort with latitude; neighbors can have very different prefixes at edges
QuadtreeRecursively split a square into 4 until each leaf has ≤ N pointsAdapts to density (Manhattan vs Sahara)In-memory structure; rebalancing on writes; harder to distribute
R-treeBalanced tree of bounding rectanglesGreat for polygons/lines (roads, buildings); PostGIS defaultOverlapping boxes; heavy writes degrade it
S2 (Google)Project sphere onto cube faces, Hilbert curve, 31 levels (0–30) of cellsNear-uniform cell areas worldwide; 64-bit cell IDs; range scans follow Hilbert localityMore complex; region coverings need a library
H3 (Uber)Hierarchical hexagons, 16 resolutions (0–15)Uniform neighbor distance (6 neighbors); great for aggregation and smoothingHexagons don't nest perfectly; not ideal for exact containment
Routing AlgorithmHow It WorksQuery Time (continental graph)Weight Update Cost
DijkstraExpand nearest-first from sourceSeconds (millions of nodes settled)Free — uses live weights
A*Dijkstra + admissible heuristic (straight-line distance / max speed)~2–5× faster than DijkstraFree
Contraction Hierarchies (CH)Precompute shortcuts by contracting nodes in importance order; bidirectional upward searchSub-millisecondFull re-preprocessing: minutes to hours
Customizable Route Planning (CRP / multi-level partitions)Partition graph into nested cells; precompute cell boundary cliques; metric customization separate from topology~1–10msRe-customize in seconds when weights change

For most production systems: S2 (or H3 for analytics) for spatial indexing, and a partition-based, customizable routing engine (CRP/MLD-style) so live traffic can update weights in seconds without redoing hours of preprocessing. Pure CH is fastest on static weights; it loses the moment traffic matters.


Executive Summary

If you only read one section, read this. Everything else in the case study elaborates the contrast below.

What This Interview Actually Tests#

Google Maps is not a geohash question. Everyone can draw a grid.

It is a freshness-vs-precomputation question across four subsystems that tests:

  • Whether you decompose the product into tiles, search, routing, and traffic — and pick one to go deep on
  • Whether you choose a spatial index for a specific query pattern rather than by reputation
  • Whether you understand that the fastest routing algorithms depend on precomputation that live traffic invalidates
  • Whether you own the data pipeline: who is paged when traffic data goes stale and ETAs silently drift

The key insight: The map changes on three clocks — the road network changes weekly, traffic changes every minute, and a user's position changes every second. Every component of the design is defined by which clock it runs on. Precompute what changes weekly; stream what changes per minute; never put per-second data on a path that expects weekly data.

The L5 vs L6 Contrast — Start Here#

BehaviorSenior (L5)Staff (L6)Principal (L7)
First moveDraws geohash grid + database of placesSplits into tiles, search, routing, traffic; asks which one we're designing and commits to routing + ETAAsks which products consume location (maps, rides, delivery, ads) and whether geo is a shared platform
Spatial index"Geohash, it's simple"Picks per query: S2 coverings for region queries, H3 for traffic aggregation, R-tree for polygon containment; names the geohash edge problemStandardizes one cell system org-wide so datasets join without reprojection
Routing"Dijkstra / A*"CRP-style partitioned graph: preprocessing on topology, seconds-level customization on live weights; explains why pure CH breaks with trafficPrices the routing fleet vs precompute cluster; decides when a regional partition becomes its own team
Traffic"Average the GPS speeds"Map matching, min-probe thresholds, 1–5 min windows, blend with historical profiles; privacy thresholdsOwns the data contract with privacy/legal; decides retention and aggregation standards
Failure"Replicate the database"Stale traffic is the silent failure: ETAs drift without errors; traffic_segment_age_p95 pagesDesigns graceful degradation posture: historical-only ETAs, published accuracy SLO, error budget
Ownership"The maps team"Map data (ingest/QA), routing engine, traffic pipeline, tile serving are four owners with contractsRedraws boundaries: map data as a platform with SLAs to every downstream product
Why "first move" separates levels

L5: Starts drawing a geohash grid and a places table. That's the proximity-search subsystem — a fine answer to "find nearby restaurants," a tiny slice of "design Google Maps."

L6: "Maps is four systems: tiles, search, routing, and live traffic/ETA. Tiles are a CDN problem I can cover in two minutes. Search is proximity plus text relevance. Routing with live traffic is where precomputation fights freshness, so I'll go deep there unless you'd rather I focus elsewhere."

L7: Adds "and location is consumed by five other products — delivery, ride-hailing, ads — so the road graph and traffic feed are platform assets with SLAs, not internals of the Maps app."

Why "routing" separates levels

L5: "A* with a good heuristic." On a graph with ~10⁸ edges, A* still settles millions of nodes for a cross-country route — seconds of CPU per query. At 100K route requests/sec that's a data-center-sized fleet.

L6: "Precomputation is mandatory at this scale. Contraction Hierarchies give sub-millisecond queries but bake edge weights into shortcuts — any traffic change means re-preprocessing. So I split topology from metric: partition the graph into nested cells, precompute boundary structure once per map release, and re-customize the metric every 1–5 minutes from live traffic. Queries run in a few milliseconds and see traffic minutes old, not hours."

Why "failure" separates levels

L5: Designs for crashes: replicas, load balancers, retries.

L6: Designs for wrongness. The worst Maps failure is not a 500 — it's a confident ETA based on traffic data that stopped updating 40 minutes ago. "Every segment weight carries an age. If the traffic pipeline stalls, routing automatically falls back to historical profiles per segment once live data exceeds 10 minutes old, and we page on traffic_segment_age_p95 > 5 min."

The Staff Positions#

PositionRationale
Decompose into tiles, search, routing, traffic — go deep on oneEach has a different bottleneck; designing all four shallowly is L5
S2 for storage/range queries, H3 for traffic aggregationUniform-ish cells for indexing; uniform neighbors for smoothing. Geohash only when the stack can only do string prefixes
Partitioned, customizable routing over pure CHTraffic changes weights every minute; re-customization in seconds beats re-preprocessing in hours
Vector tiles, pre-rendered for z0–z14, overzoomed beyondClient renders styling; 10–50× fewer tile variants than raster × styles × languages
Every traffic value carries an age; stale falls back to historicalFreshness must be visible to the router, or ETAs silently rot
Privacy thresholds are design inputsMin probes per segment-window (k ≥ 5), trimmed trip ends; legal signs off
Map data releases are canary-rolled like codeA bad road edit can route 1M drivers into a closed bridge

The Three Intents#

IntentConstraintStrategyFailure ModeCorrectness Bar
Map display (tiles)Read-heavy (~99.9% reads), global, sub-100msPre-rendered vector tiles in object storage behind a CDN; versioned by map releaseStale or missing tiles; CDN cache stampede after releaseVisually correct; minutes-to-days staleness OK
Place search / proximityLow latency, text + geo relevanceS2-cell-sharded index + text index; rank by distance, rating, open-nowHot cells (Times Square); stale POI data"Good enough" top-10; freshness in hours
Routing + live ETA100K+ queries/sec, ms-level, minute-fresh trafficPartitioned graph with customizable metric; streaming traffic pipeline; ML ETA correctionSilent staleness; bad map edit; unroutable regionsETA within ±10% for 90% of trips; no routes through closures

🎯 Staff Move: "I'll design routing plus live ETA, because it's where the freshness-versus-precomputation tradeoff is sharpest and where a silent failure hurts millions of drivers. I'll cover tiles as a CDN problem in two minutes and treat place search as a spatial index I'll reuse for map matching."

The Five Fault Lines#

#Fault LineThe Tension
1Precompute vs Freshness (routing)Sub-ms queries on stale weights vs slow queries on live weights
2Spatial Index ChoiceSimple string prefixes (geohash) vs uniform cells (S2) vs uniform neighbors (H3) vs adaptive density (quadtree/R-tree)
3Traffic Freshness vs Accuracy vs PrivacyShorter windows are fresher and noisier; fewer probes per window leak individual trips
4Pre-rendered vs On-demand TilesStorage and release time vs render CPU and cache misses
5Global Graph vs Regional PartitionsOne graph answers every route; regional graphs fit in memory and fail independently

In the Wild: Real Production Systems#

Why this section belongs here: These are the public anchors interviewers recognize.

Google — S2 Geometry and Crowd-Sourced Traffic#

Google open-sourced the S2 geometry library, which maps the sphere onto six cube faces and orders cells along a Hilbert curve across 31 levels, giving 64-bit cell IDs with strong locality. MongoDB's 2dsphere index is built on S2, which is a useful proof that it works as a database index. Google has publicly described using aggregated, anonymized location data from phones to estimate live traffic, and in 2020 Google and DeepMind published work applying graph neural networks to ETA prediction, reporting meaningful accuracy improvements (up to ~50% fewer inaccurate ETAs in some cities).

Staff insight: Google's traffic is a data-pipeline product, not a routing feature. Mentioning that ETA is "historical profile + live signal + learned correction" shows you know ETA is a prediction problem, not a shortest-path output.

Uber — H3 Hexagonal Grid#

Uber open-sourced H3 in 2018: a hierarchical hexagonal grid with 16 resolutions used for surge pricing, supply/demand forecasting, and aggregation. Hexagons have one kind of neighbor (all 6 equidistant), which makes smoothing and gradient calculations cleaner than square grids with edge-vs-corner neighbors.

Staff insight: Different cell systems for different jobs is a legitimate answer. H3 for aggregation and ML features; S2 or R-trees for exact containment and storage.

OSRM / Academic Routing — Contraction Hierarchies and Multi-Level Partitioning#

The open-source OSRM engine (on OpenStreetMap data) popularized Contraction Hierarchies — shortest paths on continental graphs in well under a millisecond — and later added a multi-level Dijkstra (MLD) mode inspired by Customizable Route Planning (Microsoft Research, Delling et al.), trading some query speed for the ability to update weights quickly.

Staff insight: The existence of two modes in one engine is the fault line in miniature: CH when weights are static, partition-based when traffic matters.

What Interviewers Probe#

After You Say...They Will Ask...What They're Evaluating
"Geohash for nearby search""The user is at a cell boundary. What do you query?"Neighbor expansion; awareness of edge/pole distortion
"Dijkstra / A*""How long for LA to NYC? At 100K QPS?"Scale arithmetic; need for precomputation
"Contraction Hierarchies""Traffic changes every minute. Now what?"Precompute vs freshness tradeoff
"We average GPS speeds""A phone is on the frontage road, not the highway. How do you know?"Map matching; noise; min probe counts
"Tiles on a CDN""We ship a new map release. What happens to cache hit rate?"Versioning, pre-warming, stampede control
"ETA = path length / speed""It's 4:55pm. The trip is 50 minutes. Which traffic do you use at minute 40?"Time-dependent routing; prediction

System Architecture Overview#

Diagram: System Architecture Overview

Reading the diagram: Three clocks. Weekly: map data builds the road graph artifact (partitioned and preprocessed for hours), tiles, and the search index — rolled out like a code release. Per-minute: GPS probes stream through map matching and windowed aggregation into live segment weights, which the routing service folds into its metric every 1–2 minutes. Per-request: routing runs on the in-memory graph with current weights; the ETA model corrects the raw path time. Observability watches the age of traffic data, not just errors.

Quick-Reference: The 30-Second Cheat Sheet#

TopicThe L5 AnswerThe L6 Answer — Say This
ScopeDesign everything"Four subsystems; I'll go deep on routing + live ETA."
Spatial index"Geohash""S2 cell IDs for storage and coverings; H3 for traffic aggregation; R-tree/PostGIS for polygon ops in the map-data pipeline."
Routing"A*""Partitioned graph with precomputed topology and a metric customized every 1–2 minutes from live traffic."
Traffic"Average speeds""Map-match probes with an HMM, 1-min windows, ≥5 probes, blend with historical profile weighted by probe count."
ETA"Distance / speed""Time-dependent: live speeds near-term, historical profiles for later legs, ML correction on top. Target ±10% for 90% of trips."
Tiles"Render on request""Vector tiles pre-built per release, CDN, versioned URLs, pre-warm top 5% of tiles."
Failure"Replicas""Stale traffic is the silent killer — segment age is a first-class metric with automatic fallback."

Key Numbers Worth Memorizing#

MetricValueWhy It Matters
Geohash precision 6 / 7 / 8~1.2km×0.6km / ~153m×153m / ~38m×19mChoose cell size to match query radius
S2 levels31 (0–30); level 30 ≈ 1cm²64-bit IDs, Hilbert locality
H3 resolutions16 (0–15); res 8 ≈ 0.74km², res 9 ≈ 0.1km²Typical traffic/surge aggregation sizes
Tiles per zoom level4^z (z14 ≈ 268M, z18 ≈ 69B)Why you pre-render to ~z14 and overzoom
Meters per pixel at z0 (equator, 256px)~156km; halves each zoom (~0.6m at z18)Sizing zoom to use case
Continental road graph10⁷–10⁸ nodes; planet routable graph ~10⁸+ edgesDoesn't fit on one small box with all precomputation
Dijkstra cross-continentsecondsWhy precomputation is mandatory
CH query<1msFastest, but static weights
Partition-based query / customization~1–10ms / secondsThe live-traffic compromise
GPS probe interval while navigating1–5s~1M+ pings/s at large scale
Traffic window / min probes1–5 min / k ≥ 3–5Freshness vs noise vs privacy
ETA accuracy target±10% for ~90% of tripsProduct-level correctness bar

Interview Walkthrough

The most common mistake: Candidates try to design tiles, search, routing, traffic, Street View and offline maps in 45 minutes and go 2 inches deep on each. The phases below scope in 3 minutes, sketch in 10, and spend 25+ on the subsystem that decides your level.


Phase 1: Requirements & Framing (2–3 minutes)#

State the functional scope in one breath:

"Users see a map, search for places, get driving directions with an ETA, and get rerouted when traffic changes."

Then decompose and commit:

"That's four subsystems on three clocks. Tiles and search change when map data changes — weekly. Traffic changes every minute. A navigating user's position changes every second. I'll cover tiles quickly as a CDN problem and go deep on routing with live traffic and ETA, because that's where precomputation and freshness collide. Does that match what you want?"

Commit to non-functional targets:

"Route queries p99 under 200ms end to end, with the graph search itself under 10ms. Traffic reflected in routing within 2–3 minutes of a change. ETA within ±10% for 90% of trips. Global scale: ~1B monthly users, peak ~100K route requests/sec, ~1M GPS probes/sec from navigating devices. Map data changes must never route drivers through closed roads — that's the one correctness bar I'll treat as hard."

🎯 Staff Move: Naming the three clocks (weekly, per-minute, per-second) gives the interviewer a mental model for the rest of your answer. Every later decision — what's precomputed, what's streamed, what's cached — follows from which clock the data runs on.


Phase 2: Core Entities & API (1–2 minutes)#

  • Node: road intersection or shape point — node_id, lat, lng, s2_cell
  • Segment (edge): directed road piece — segment_id, from, to, length_m, road_class, speed_limit, restrictions, geometry
  • Turn restriction: (from_segment, via_node, to_segment, allowed)
  • SegmentSpeed: segment_id, window_start, speed_kmh, probe_count, age_s, source(live|historical)
  • Place: place_id, name, category, lat, lng, s2_cell_l16, hours, rating
  • MapRelease: release_id, graph_artifact_uri, tile_version, created_at

Routing API:

GET /v1/route?origin=lat,lng&dest=lat,lng&depart_at=now&mode=drive&alternatives=2
→ { routes: [{ polyline, distance_m, eta_s, eta_confidence, traffic_age_s, legs[] }], map_release }

Navigation session (streaming):

→ PROBE    { session_id, lat, lng, speed, heading, ts, accuracy_m }   every 1–5s
← REROUTE  { route, reason: 'faster_route' | 'off_route' | 'closure' }

Search:

GET /v1/places?q=coffee&near=lat,lng&radius_m=1500&open_now=true

🎯 Staff Move: "The route response carries traffic_age_s and the map_release it was computed on. When a user reports a bad route, support can reproduce it exactly. That's not a UX detail — it's how we debug the silent failures."


Phase 3: High-Level Architecture (≤5 minutes)#

Diagram: Phase 3: High-Level Architecture (≤5 minutes)

Walk it in 60 seconds:

  1. Tiles: vector tiles pre-built per map release, stored in object storage, served by CDN. Versioned URLs, so a release is a cache-key change, not a purge.
  2. Map data → graph: weekly build partitions the road graph into nested cells and precomputes boundary shortcuts — hours of compute, output is an immutable artifact loaded by routing servers.
  3. Traffic: navigating phones send probes to Kafka; Flink snaps them to road segments (map matching), aggregates per segment per minute, and publishes speeds with an age.
  4. Routing: servers hold the graph for their region in memory; every 1–2 minutes they re-customize the metric using live weights; queries run bidirectional search across the partition overlay in ~1–10ms.
  5. ETA: raw path time is corrected by a model trained on actual trip durations.

🎯 Staff Move: "That's the skeleton. The decisions that matter are: which routing precomputation survives live traffic, how we turn noisy GPS into trustworthy segment speeds, and how we keep a bad map release from routing a million drivers wrong. Which one first?"


Phase 4: Transition to Depth (1 minute)#

"I'd start with routing precomputation versus freshness, because it determines the fleet size and how fast traffic shows up in routes. Then the traffic pipeline — map matching and privacy thresholds. Then failure: stale traffic and bad map data."

If the interviewer wants spatial indexing specifically: "Happy to go there — I'd use S2 cell IDs for place search and map-matching candidate lookup. Let me show you how a radius query becomes a set of cell-ID ranges."


Phase 5: Deep Dives (25–30 minutes)#

Deep dive A: Routing — precompute vs freshness (8–10 min)

"A continental road graph has 10⁷–10⁸ nodes. Plain Dijkstra settles millions of nodes for a long route — seconds of CPU. At 100K QPS, that's hundreds of thousands of cores. So precomputation is non-negotiable. The question is what it costs us when weights change."

"Contraction Hierarchies: order nodes by importance, contract them, add shortcuts that preserve shortest paths. Queries are sub-millisecond. But every shortcut's weight is a sum of underlying edge weights computed under one metric. Traffic changes thousands of segment weights every minute; CH needs re-preprocessing, which takes minutes to hours for a continent. That's the freshness death."

"Partition-based customizable routing: partition the graph into cells at 2–4 nested levels using a balanced graph partitioner so each cell has few boundary nodes. Preprocessing — done weekly — computes the partition. Customization — done every 1–2 minutes — recomputes, for each cell, shortest distances between its boundary nodes under current weights. Customization is embarrassingly parallel per cell: seconds for a continent. Queries run bidirectional Dijkstra on the overlay: a few milliseconds. I'll take 5ms queries on 2-minute-old traffic over 0.5ms queries on 6-hour-old traffic."

"Who pays: the routing fleet pays ~5–10× more CPU per query than CH; users get fresh routes. For static-weight use cases — say walking directions — I can still use CH."

Deep dive B: Traffic pipeline (7–8 min)

"A GPS fix has 5–20m error in cities, worse in urban canyons. Snapping to the nearest road is wrong at interchanges and frontage roads. I use HMM map matching: candidate segments within ~50m of each fix are hidden states, emission probability decays with distance, transition probability favors routes whose network distance matches the straight-line distance between fixes. Viterbi over a sliding window of ~10 fixes. Candidate lookup uses the spatial index — segments indexed by S2 cell at ~level 16."

"Aggregation: per segment, 1-minute tumbling windows, median speed of matched traversals, require ≥5 distinct devices before publishing — below that, noise dominates and single trips become identifiable. Blend: speed = w·live + (1−w)·historical(segment, day_of_week, 15-min bucket) where w rises with probe count and falls with age. Every value is stored with age_s."

"Privacy: trim the first and last ~200m of each trip so home and work locations never enter the pipeline; rotate device IDs daily; drop raw probes after aggregation (e.g., 24h retention). Legal and privacy sign off on k and retention."

Deep dive C: Map data releases and failure (6–7 min)

"A road graph release is code. A wrong one-way flag on a bridge routes every driver the wrong way. So releases get validation — connectivity checks (no new unreachable islands > N nodes), route-diff tests on a fixed set of 100K origin-destination pairs (flag if >1% change by >20%), then canary: 1% of routing servers in one region, compare ETA error and reroute rates against control for an hour, then roll forward. Rollback is loading the previous artifact — the servers keep two in memory."

"Live closures can't wait for a weekly release, so there's an overlay: closure events (from authorities, user reports, or detected zero-flow segments) set segment weight to infinity in the live metric. That rides the per-minute customization path."

🎯 Staff Move: Every deep dive ends with an owner: "Map data team owns release validation and signs off canaries. Routing team owns the engine and query SLO. Traffic team owns freshness — they're paged on segment age, not on errors."


Phase 6: Wrap-Up (2–3 minutes)#

"Summary: three clocks. Weekly map releases build a partitioned graph artifact, tiles, and a search index, canaried like code. A per-minute streaming pipeline turns probes into segment speeds with ages and privacy thresholds. Per-request routing uses a customizable partitioned graph so traffic shows up in ~2 minutes, and an ETA model corrects the raw path time. Next I'd build: time-dependent routing for long trips so minute-40 uses predicted rather than current traffic; incident detection from sudden speed drops; and an ETA accuracy dashboard by region so we know where we're wrong before users tell us."


Common Timing Mistakes#

MistakeTime LostFix
Designing all four subsystems20+ minScope to one; give tiles 2 minutes
Explaining geohash encoding bit by bit5–8 min"Interleaved bits, prefix = containing cell." Move on
Deriving Dijkstra5 minAssume it; go to why it's too slow
Tile rendering pipeline details5–10 min"Vector tiles per release on a CDN"
Never mentioning stalenessfatalSay "every weight carries an age" in Phase 3

1. The Staff Lens#

1.1 Why This Problem Exists in Staff Interviews#

It's the canonical "too big to design" prompt. The first signal is whether the candidate decomposes and scopes, or drowns. The second is whether they understand that precomputation — the only way to answer routing at scale — creates a freshness debt, and that the most dangerous failure is a quietly wrong answer rather than an error. The third is organizational: map data, traffic, routing, and tiles are separate teams with separate clocks, and the contracts between them are where incidents happen.

1.2 The L5 vs L6 Contrast — Visual#

Diagram: 1.2 The L5 vs L6 Contrast — Visual

1.3 The Staff Question That Cuts Through Everything#

"How old is the data this answer was computed from, and who finds out when it's too old?"

It forces the three-clock model, the age-tagging of every traffic value, the fallback to historical profiles, the release versioning on routes, and the freshness metric that pages the traffic team. A candidate who asks it is designing for wrongness, not just outages.


2. Problem Framing & Intent#

2.1 The Three Intents — Explained#

Map display. A pure read-scaling problem. Tiles are immutable per release, so they cache perfectly at the CDN with versioned URLs. The interesting decisions are vector vs raster, zoom levels to pre-render, and how to avoid a cache stampede when a release flips versions. Victims: users on slow devices (vector rendering costs client CPU) and the CDN bill.

Place search. A spatial index plus text relevance. The spatial part — "within 1.5km" — is solved by covering the circle with S2 cells and scanning cell-ID ranges; the hard part is ranking (distance vs rating vs open-now vs ads) and freshness of business data. Victims: dense cells (Manhattan) where a coarse cell holds 50K places, and businesses whose hours are stale.

Routing + live ETA (default). A graph problem at massive scale fused with a streaming data problem. Victims of the design choices: the routing fleet's CPU (customizable routing costs more per query than CH), drivers when traffic is stale, and privacy if probe aggregation is careless.

2.2 When NOT to Use Heavy Geospatial Infrastructure#

SituationBetter ChoiceWhy
Store locator with 2,000 storesPostGIS or even a brute-force distance scan2K points × haversine is microseconds
"Nearby drivers" with 5-second freshnessIn-memory geohash/H3 buckets in Redis (Proximity Matching)Moving points need update-optimized structures, not a routing graph
Delivery ETA in one cityBuy a routing API (per-request pricing) until volume justifies buildingRouting engines are years of work
Analytics heatmapsH3 aggregation in a warehouseNo need for low-latency serving
Geofencing a few hundred polygonsR-tree in processTiny dataset

🎯 Staff Move: "If we're a delivery company with 5M routes a day, we buy routing from a provider until the bill passes roughly the cost of a 6–8 person routing team. Building a routing engine is a multi-year commitment, not a sprint."

2.3 What the Interviewer Leaves Underspecified#

Unstated AssumptionWhy It MattersWhat to Say
Which subsystemFour different designs"I'll go deep on routing + ETA"
Travel modesWalking/transit need different graphs and weights"Driving first; walking reuses graph with static weights via CH"
Departure timeNow vs future changes traffic source"Live for depart-now; historical profiles for future trips"
Region coverageGraph size and partitioning"Global, served per region with cross-region handling"
Freshness SLOPipeline window and fleet size"Traffic in routes within 2–3 min"
Privacy constraintsProbe retention and thresholds"k ≥ 5, trip-end trimming, 24h raw retention"
OfflineClient-side routing on downloaded regions"Out of scope; mention as later"

2.4 Precise Terminology#

TermMeaningCommon Confusion
Cell coveringSet of cells (possibly mixed levels) whose union covers a regionNot just "the 9 neighbors" — coverings adapt cell sizes
Space-filling curveMaps 2D cells to 1D order (Z-order for geohash, Hilbert for S2)Hilbert has better locality; fewer range scans
Map matchingInferring the road segments a GPS trace traveledNot "nearest road" — uses path continuity
ProbeOne GPS fix from a deviceSpeeds come from consecutive probes, not one
MetricThe weight function on edges (time under current traffic)Topology (graph shape) vs metric (weights) is the key split
CustomizationRecomputing cell-boundary distances for a new metricDifferent from preprocessing (partitioning)
ShortcutPrecomputed edge representing a shortest subpathIts weight is stale when underlying weights change
Time-dependent routingEdge weight is a function of arrival timeNeeded for long trips crossing rush hour boundaries
OverzoomRendering zoom z+n from the z tile's vector dataWhy you don't pre-render z15–z20
Web MercatorEPSG:3857 projection used by web tilesDistorts area at high latitudes; not for distance math

3. The Five Fault Lines#

3.1 Fault Line 1: Precompute vs Freshness (Routing)#

StrategyWhat WorksWhat BreaksWho Pays
Dijkstra / A* on live weightsAlways fresh; no preprocessingSeconds per long query; fleet cost explodes at 100K QPSInfra budget; users (latency)
Contraction Hierarchies<1ms queries; small fleetWeights baked into shortcuts; traffic requires minutes–hours of re-preprocessingDrivers (stale routes)
Partitioned + customizable metric (CRP/MLD)~1–10ms queries; re-customize in seconds5–10× query CPU vs CH; more memory for overlayRouting fleet budget
CH + live-traffic post-correctionFast; somewhat traffic-awareCan't route around jams — only re-estimates ETA on a stale pathDrivers stuck in a jam with a correct ETA

The Staff default: partitioned graph with customizable metric for driving; CH for modes with static weights (walking, cycling).

When to deviate: a small single-city deployment can run A* with live weights — 50K-node graph, queries in ~10ms. A logistics optimizer running millions of many-to-many matrix queries overnight may prefer CH on historical weights.

Diagram: 3.1 Fault Line 1: Precompute vs Freshness (Routing)

3.2 Fault Line 2: Spatial Index Choice#

IndexBest ForWhat BreaksWho Pays
GeohashStacks that only support string prefix (Redis sorted sets, DynamoDB sort keys)Edge adjacency: two points 1m apart can share no prefix; cells shrink toward polesQuery code (must scan 8 neighbors); high-latitude users
S2Storage, coverings of arbitrary regions, global uniformityLibrary dependency; covering tuning (max cells, min/max level)Engineers learning it
H3Aggregation, smoothing, ML features, surge/traffic heatmapsChildren don't exactly tile parent; not ideal for exact containmentAnalysts correcting for boundary error
QuadtreeIn-memory adaptive density (moving objects, dense cities)Hard to distribute; rebalancingService owner
R-tree (PostGIS)Polygons, line geometry, containmentWrite-heavy workloads; one DB node limitsDBA / storage team

The Staff default: S2 cell IDs as the storage key for places and road segments (range scans on a sorted key), H3 for traffic/analytics aggregation, R-tree inside the map-data authoring pipeline where polygons live.

When to deviate: if the only store is Redis, geohash (Redis GEO commands use 52-bit geohash under the hood) is pragmatic. Name the neighbor-scan cost.

🎯 Staff Move: "A 1.5km radius query becomes an S2 covering of ~8–20 cells at mixed levels, which becomes ~8–20 contiguous key ranges. Hilbert ordering means most ranges are long and contiguous, so it's a handful of range scans per shard, not a scatter of point lookups."

3.3 Fault Line 3: Traffic Freshness vs Accuracy vs Privacy#

WindowFreshnessNoisePrivacy RiskWho Pays
30 sExcellentHigh — few probes, one slow car dominatesHigh — single trips visibleDrivers (jittery reroutes); users (privacy)
1–2 min, k ≥ 5GoodModerateLowMinor-road coverage (often below k)
5 min, k ≥ 10Slow for incidentsLowVery lowDrivers hitting fresh jams
Historical onlyNoneLowestNoneEveryone during incidents

The Staff default: 1-minute windows with k ≥ 5 distinct devices, blended with historical profiles weighted by probe count. Minor roads rarely hit k — they use historical profiles, which is fine because they rarely drive route choice.

When to deviate: incident detection can use a separate, faster signal (e.g., sudden multi-device stop on a highway) that sets a closure/slowdown flag without publishing individual speeds.

3.4 Fault Line 4: Pre-rendered vs On-demand Tiles#

StrategyWhat WorksWhat BreaksWho Pays
Raster, pre-rendered all zoomsSimple clientsPetabytes; × styles × languages × dark mode; release takes daysStorage + release velocity
Vector, pre-rendered z0–z14, overzoom10–50× fewer variants; client styles; fast releasesClient CPU/GPU; older devicesLow-end device users
On-demand render with cacheNo pre-render costCache stampede after release; tail latency on missesOrigin fleet; users on cold tiles

The Staff default: vector tiles pre-rendered through ~z14, overzoom on client, versioned URLs per release, pre-warm the top ~5% of tiles (which serve the large majority of requests) in the CDN before flipping the version.

3.5 Fault Line 5: Global Graph vs Regional Partitions#

StrategyWhat WorksWhat BreaksWho Pays
One global graph per serverAny route answered anywhereMemory: planet graph + overlay + metrics is tens to 100+ GB; slow loads; one bad release is globalFleet cost; global blast radius
Regional graphs (continent/country) with overlapFits memory; independent releases; regional blast radiusCross-region routes need stitching or a coarse top-level graphRouting team complexity

The Staff default: regional graphs sized to fit comfortably in memory, overlapping at borders, plus a coarse highways-only global overlay for inter-region routes (rare — most driving routes are <100km).


4. Failure Modes & Operational Reality#

4.1 Silent Staleness — The Traffic Pipeline Stalls#

t=0:       Flink job checkpoint fails; aggregator stuck, not crashed
t=+1min:   Live weights stop updating; routing keeps using last values
t=+20min:  Evening rush builds; routes still see 3pm speeds
t=+40min:  ETA error p50 rises from 7% to 22%; no 5xx, no alerts on errors
t=+55min:  Social media reports; on-call notices from ETA dashboard

Detection: traffic_segment_age_p95 > 5 min pages; traffic_publish_lag_seconds; eta_abs_error_pct by region (computed from completed trips, ~15–60 min delay — a lagging indicator).

Mitigation: Routing treats weights older than 10 min as absent and falls back to historical profiles per segment — degraded but not wrong-by-hours. Restart the aggregator from the last good checkpoint; skip backlog older than 5 min (stale traffic has no value).

Prevention: Age-based fallback built into the metric; a heartbeat segment set (major highways) whose age is checked every 30s. Owner: traffic pipeline team.

4.2 Bad Map Release — The Wrong-Way Bridge#

t=0:       Release R-2291 flips a one-way flag on a major bridge
t=+15min:  Canary skipped (holiday freeze exception); rollout to 100%
t=+30min:  Routes send drivers against traffic direction onto bridge approach
t=+45min:  Reroute rate on region spikes 6×; driver reports flood in
t=+50min:  Rollback to R-2290 (servers keep previous artifact in memory)

Detection: reroute_rate by region; off_route_rate; route-diff test failures; user "wrong route" reports per segment.

Mitigation: Instant rollback to the in-memory previous artifact. Live-closure overlay blocks the segment within 2 minutes while the fix ships.

Prevention: Route-diff tests on 100K canonical OD pairs; connectivity and direction-flip audits on high-traffic segments; canary with metric comparison is non-skippable. Owner: map data team (release), routing team (rollback mechanism).

4.3 Hot Cell — Search Meltdown at an Event#

Scenario: A stadium concert ends; 60,000 people search "parking" and "restaurants" within the same few S2 cells.

Detection: search_qps_by_cell top-K; search_shard_cpu; p99 latency on the shard holding that cell range.

Mitigation: Cache search results per (cell, category, rounded-radius) for 60s; replicate hot cell ranges to more replicas; shard by S2 cell at a level where dense areas split into more shards.

Prevention: Shard boundaries by load, not by equal cell counts — split Manhattan finer than Montana. Owner: search team.

4.4 Probe Flood — A Bad Client Release#

Scenario: App v9.3 sends probes every 100ms instead of every 1s. Probe volume 10×.

t=0:      v9.3 rolls out to 20% of Android
t=+2hr:   Kafka ingest 3× normal; Flink map-matching lag rising
t=+3hr:   traffic_segment_age_p95 crosses 5 min → page

Detection: probe_ingest_rate per app version; mapmatch_consumer_lag.

Mitigation: Server-side sampling per device (keep 1 probe/sec); config-pushed client rate limit; per-version quotas at the gateway. See Rate Limiting.

Prevention: Probe rate is a server-controlled config, not a client constant. Owner: traffic team + mobile team.

4.5 Tile Release Stampede#

Scenario: New tile version flips at 00:00 UTC; CDN has 0% hit rate on new URLs; origin sees 20× load.

Mitigation/Prevention: Pre-warm popular tiles before flip; flip per-region gradually; request coalescing at CDN shield tier. See CDN & Edge Caching. Owner: tiles team.

4.6 Operational Reality Matrix#

FailureDetection SignalBlast RadiusMitigationOwner
Traffic pipeline stalltraffic_segment_age_p95All routes in affected regionsHistorical fallback; restart from checkpointTraffic
Bad map releasereroute_rate, route-diff testsRegion of releaseRoll back artifact; closure overlayMap data + Routing
Routing server OOM on loadgraph_load_failures, capacity dropOne region's fleet capacityKeep N-1 artifacts; stagger loadsRouting
Hot search cellsearch_qps_by_cellShardCache + replicate + finer shardingSearch
Probe floodprobe_ingest_rate by versionTraffic freshness globallyServer-side samplingTraffic + Mobile
Tile stampedeCDN hit rate, origin QPSTile latency region-widePre-warm, staged flipTiles
ETA model regressioneta_abs_error_pct by regionAll ETAs where deployedRoll back model; shadow before launchETA/ML
Privacy breach (k too low)Audit of published segments below kLegal/regulatoryStop publishing; re-aggregateTraffic + Privacy

5. Evaluation Rubric#

5.1 Level-Based Signals#

DimensionSenior (L5)Staff (L6)Principal (L7)
ScopingCovers everything shallowlyDecomposes into 4 subsystems, commits to oneIdentifies geo as a shared platform across products
Spatial indexGeohash with neighbor scanChooses index per query; explains coverings and Hilbert localityStandardizes cell system org-wide for data joins
RoutingA* / DijkstraCH vs customizable partitioned routing with freshness tradeoffPrices fleet vs freshness; decides build vs buy by region
TrafficAverages speedsMap matching, windows, k-anonymity, historical blend, agePrivacy data contract, retention standard, legal sign-off
FailureCrashes and replicasSilent staleness, bad releases, canary + rollbackAccuracy SLO with error budget; release governance across map data teams
Ownership"Maps team"Four owning teams with contractsRedraws map-data platform boundaries and SLAs

5.2 Strong Hire Signals#

SignalWhat It Sounds Like
Three clocks"Map data is weekly, traffic is per-minute, position is per-second. Each component lives on one clock."
Precompute vs freshness"CH bakes weights into shortcuts; I split topology from metric so traffic is a seconds-level customization."
Designs for wrongness"Every weight carries an age; stale weights fall back to historical, and age pages the traffic team."
Map data as code"Releases get route-diff tests and canaries; rollback is loading the previous artifact."
Privacy as a design input"k ≥ 5 per segment-minute, trip-end trimming, 24h raw retention — privacy signs off."

5.3 Lean No-Hire Signals#

SignalWhy It Misses the Bar
Designs all subsystems at equal depthNo prioritization; runs out of time
"Dijkstra" without scale mathDoesn't see why precomputation is needed
"Nearest road" for GPS snappingWrong at every interchange
No staleness handlingMisses the most damaging failure mode
Treats map data as a static DBIgnores releases, validation, rollbacks

5.4 Common False Positives#

  • Knowing geohash bit interleaving ≠ spatial index judgment. Ask when they'd not use it.
  • Reciting CH ≠ understanding traffic. Ask what happens when 5,000 segment weights change.
  • An ML ETA model ≠ an ETA system. Ask how they detect it's wrong in one city.
  • "We use PostGIS" ≠ a design. PostGIS is great for the authoring pipeline and weak at 100K QPS serving.

6. Interview Flow & Pivots#

6.1 Typical 45-Minute Shape#

PhaseTimeGoal
Framing0–3 minDecompose, three clocks, commit to routing + ETA
Entities & API3–5 minSegment, speed with age, route response with release
Architecture5–10 minWeekly build, streaming traffic, routing, ETA
Deep dive 110–20 minPrecompute vs freshness
Deep dive 220–30 minTraffic pipeline: map matching, windows, privacy
Deep dive 330–40 minFailure: staleness, bad releases
Wrap-up40–45 minTime-dependent routing, accuracy dashboards

6.2 How Interviewers Pivot — And What They're Testing#

PivotWhat They're TestingStrong Response
"Find restaurants within 1km."Spatial index fluencyS2 covering → key ranges → filter by exact distance → rank
"A user is navigating. Traffic worsens ahead."Reroute logicPeriodic re-query every ~1–2 min for active sessions; offer reroute if savings > max(2 min, 10%)
"The trip is 3 hours through two rush hours."Time-dependent routingPredicted/historical speeds by arrival time per leg
"Offline maps."Client-side routingDownload regional graph + CH (static weights); no traffic
"Uber wants to use our ETA."Platform thinkingAPI with SLA, quota, versioned ETA model, consumer-specific accuracy reporting

6.3 What to Deliberately Skip#

  • Map rendering styles and label placement
  • Street View, satellite imagery pipelines
  • Transit schedule ingestion (GTFS) unless asked
  • Exact HMM math — name emission/transition and move on

6.4 Follow-Up Questions to Expect#

  1. "How do you answer 'find within 2km' if the user is on a geohash cell boundary?"
  2. "Why not just use A* with a better heuristic?"
  3. "How quickly does a new traffic jam affect routes?"
  4. "How do you know GPS pings are on the highway and not the parallel street?"
  5. "How do you roll out a new road network without breaking routes?"
  6. "How do you prevent the traffic data from revealing where individuals live?"
  7. "What happens to ETAs if the traffic pipeline dies?"

7. Active Drills#

Drill 1: The Opening#

Prompt: "Design Google Maps."

Staff Answer

"Google Maps is four subsystems on three clocks. Tiles, place search, and the road graph change when map data changes — roughly weekly. Traffic changes every minute. A navigating user's position changes every second. Tiles are a CDN problem: vector tiles pre-built per release, versioned URLs. I'll go deep on routing with live traffic and ETA, because that's where precomputation fights freshness and where a silent failure misroutes millions of drivers. Targets: route query p99 under 200ms end to end, traffic visible in routes within 2–3 minutes, ETA within ±10% for 90% of trips, and a hard rule that map releases never route through closed or wrong-way roads."

Why this is L6:

  • Decomposes, scopes, and commits in under a minute
  • Three-clock model frames every later decision
  • States a correctness bar (release safety) alongside latency

What L7 adds:

  • "Delivery, rides, and ads will all consume the road graph and traffic feed — I'd define those as platform products with SLAs."
  • Names the dominant cost: routing fleet CPU and the traffic pipeline, not tiles
❌ Common L5 Trap

"We store places with geohashes in a database and use A* for directions."

Why this misses: It answers "find nearby" and "shortest path on a small graph." The interviewer asks "LA to New York at 100K QPS with live traffic?" and the design has no answer.


Drill 2: Radius Search with a Spatial Index#

Prompt: "Find all coffee shops within 1km of the user. Walk me through the index."

Staff Answer

Places are keyed by S2 cell ID at level ~16 (cells ~150m across) in a sorted store, sharded by cell-ID range. Query: build a 1km spherical cap, ask the S2 library for a covering with ≤16 cells between levels 12 and 16 — dense small cells near the edge, bigger cells in the middle. Each covering cell maps to one contiguous range of level-16 IDs (children of a cell are a contiguous ID range thanks to the Hilbert ordering). So the query is ~16 range scans, usually hitting 1–3 shards. Filter candidates by exact haversine distance (coverings over-approximate by ~10–30%), filter category=coffee, rank by a blend of distance, rating, and open-now, return top 20.

With geohash instead: pick precision 6 (~1.2×0.6km), query the user's cell plus 8 neighbors, filter by distance. Works, but at high latitudes cells get narrow and the 9-cell box over-fetches more.

Why this is L6:

  • Covering → ranges → shards → exact filter → rank, with numbers
  • Explains why children are contiguous ranges (the property that makes it fast)
  • Can fall back to geohash and name its costs

What L7 adds:

  • One cell system across the org so places, traffic, and ads data join on cell ID without reprojection
  • Shard split policy by load, owned by the search platform, not per-product

Drill 3: Why Not A*?#

Prompt: "A* with a straight-line heuristic is optimal. Why do you need anything more?"

Staff Answer

A* is optimal in correctness, not in work done. On a continental graph with ~50M nodes, a 1,000km route still settles millions of nodes because the straight-line heuristic divided by max speed is weak — highways are a small fraction of edges. That's ~1s of CPU per query. At 100K QPS peak, that's ~100K cores busy. Precomputation-based methods settle a few thousand nodes: CH <1ms, partition-based ~1–10ms. The fleet goes from hundreds of thousands of cores to hundreds or low thousands. A* is still fine for short, local queries or small graphs — say, final-mile routing inside one city cell.

Why this is L6:

  • Distinguishes optimality from cost
  • Converts per-query CPU into fleet size

What L7 adds:

  • Prices it: 100K cores vs ~2K cores is the difference between a line item the CFO asks about and one they don't
  • Notes that routing algorithm choice is a hiring decision (routing experts are rare)

Drill 4: Traffic Changes Every Minute#

Prompt: "You chose Contraction Hierarchies. A major highway jams. What happens?"

Staff Answer

That's why I wouldn't choose pure CH for driving. CH shortcuts store summed weights under the metric used at preprocessing time. When the highway jams, every shortcut passing over those segments is wrong, and re-preprocessing a continent takes minutes to hours. Options: (1) accept stale routing and only re-estimate ETA on the returned path — drivers get a correct ETA for a bad route; (2) switch to a partition-based customizable scheme — topology preprocessed weekly, metric customized per cell every 1–2 minutes in seconds of parallel compute. I pick (2) for driving. For walking and cycling, where weights are static, CH is perfect.

Why this is L6:

  • Identifies the exact mechanism that makes CH stale
  • Offers the degraded alternative and names its victim

What L7 adds:

  • Makes the customization interval an SLO knob with a price: 1-min vs 5-min customization is ~5× compute on the customization cluster
  • Sequences the migration from CH to customizable routing as a 2-quarter program with shadow traffic

Drill 5: Map Matching#

Prompt: "A phone reports a point 15m from a highway and 10m from the frontage road next to it. Which road is the car on?"

Staff Answer

You can't tell from one point, and nearest-road picks the frontage road — wrong if the car's doing 110 km/h. Use a Hidden Markov Model over the trace: candidate segments within ~50m of each fix (looked up via the S2-indexed segment table) are hidden states. Emission probability: Gaussian on distance to the segment (σ ≈ 5–10m for urban GPS). Transition probability: favors candidate pairs where the network distance between them is close to the great-circle distance between the fixes — a car can't teleport off the highway and back. Viterbi over a sliding window of ~10 fixes. Speed (110 km/h) and heading add evidence. Result: highway with high confidence. Run in Flink keyed by device session, emitting segment traversals with entry/exit times.

Why this is L6:

  • Rejects nearest-road with a concrete counterexample
  • Names the model, the parameters, and where it runs

What L7 adds:

  • Map-matching quality is a shared dependency for traffic, ETA training, and delivery tracking — one team owns it with an accuracy metric consumers can see
  • Negotiates probe collection frequency with mobile (battery) as a cross-org tradeoff

Drill 6: Hot Region — Stadium Letting Out#

Prompt: "70,000 people leave a stadium. They all request routes and search simultaneously."

Staff Answer

Routing is fine: routes are CPU-bound per query on replicated regional graphs, and 70K requests over ~10 minutes is ~120 QPS extra — noise against 100K QPS global. Search is where it hurts: all queries hit the same few S2 cells, so the same shard range. Fix: cache results keyed by (cell at level 14, category, radius bucket) for 60s — near-100% hit rate for identical-ish queries; read replicas for hot shard ranges; load-aware shard splitting. Traffic: 70K phones generating probes in one small area is great for traffic quality but a burst for map matching — Flink keyed by device spreads it fine. The real product issue is that routing everyone onto the same exit road creates the jam it predicts — mention it as a known limit of per-user routing.

Why this is L6:

  • Sizes each subsystem instead of assuming all are hot
  • Identifies search as the real hotspot and caches correctly

What L7 adds:

  • System-optimal vs user-optimal routing is a product/policy question (and a city-partnership one) — not a fix to slip into a sprint
  • Event calendars feed capacity planning across search and routing

Drill 7: Build vs Buy#

Prompt: "We're a food-delivery company. Build routing and ETA or buy?"

Staff Answer

Buy routing first. A production routing engine with live traffic is a multi-year investment and a rare skill set. At 10M route calls/day on a commercial API at ~$1–5 per 1,000 calls, that's ~$0.3–1.5M/year — roughly a 4–8 person team's cost. Build ETA correction on top: our differentiator is delivery-specific time (restaurant prep, parking, walking to the door, elevator), not road driving time. Our own trip data trains that model. Revisit building routing when API cost exceeds ~2× a routing team, or when we need something vendors won't provide (e.g., bike-courier paths). If we build, start from an open-source engine on OpenStreetMap data.

Why this is L6:

  • Separates commodity (road routing) from differentiated (last-mile ETA)
  • Break-even stated in dollars and headcount

What L7 adds:

  • Vendor exit plan: our ETA model consumes vendor route output through an abstraction so we can swap providers
  • Contract terms on data usage — whether our trip data trains the vendor's model

Drill 8: Shipping a Map Release Safely#

Prompt: "A new map release is ready. How do you ship it?"

Staff Answer

Treat it as a deploy. (1) Validation: graph connectivity (no new disconnected components above 1,000 nodes), direction-flip audit on top-10K traffic segments, route-diff tests on 100K canonical OD pairs — block if >1% of routes change by >20% without an explaining edit. (2) Build artifacts: partitioned graph, tiles, search index — all tagged with release ID. (3) Canary: load on 1% of routing servers in one region; compare reroute rate, off-route rate, and ETA error vs control for 1–2 hours. (4) Staged rollout: region by region. (5) Rollback: servers keep the previous artifact in memory; flip back in seconds. Live closures bypass releases via the per-minute overlay.

Why this is L6:

  • Map data treated as code, with quantified gates
  • Rollback is instantaneous by design

What L7 adds:

  • Release governance across dozens of map data sources (authorities, user edits, imagery) with a clear owner for each class of edit
  • A "no Friday releases" style policy is too blunt; instead an error-budget-based release freeze by region

Drill 9: Cost#

Prompt: "The routing fleet costs $4M/month. Cut it 30%."

Staff Answer

First, where does the CPU go: queries vs customization vs graph loading. Levers: (1) cache routes for identical (origin cell, destination cell, 5-min time bucket) pairs — commutes and popular destinations repeat; 10–20% hit rate on origin/destination at S2 level ~13. (2) Use CH for static-weight modes (walking, cycling) — cheaper per query. (3) Reduce reroute polling for active navigation from every 30s to every 2 min unless a traffic change touches the route's segments (push-based invalidation). (4) Right-size regional graphs so small regions share servers. (5) Customize only cells whose weights changed beyond a threshold. Expect 30–40% combined.

Why this is L6:

  • Diagnoses before cutting
  • Multiple quantified levers, none sacrificing freshness blindly

What L7 adds:

  • Makes $/1,000 routes a tracked unit metric; ties fleet cost to product decisions (reroute frequency is a product knob)
  • Negotiates committed-use pricing with the cloud provider once the baseline is stable

Drill 10: Multi-Region and Cross-Border Routes#

Prompt: "Routes cross from France to Germany. Your graphs are regional."

Staff Answer

Regional graphs overlap by a border buffer (~50–100km) so most cross-border trips fit inside one region's graph. For long trips (Paris to Berlin), a coarse top-level overlay of major highways stitched across regions finds the corridor; then each region's detailed graph computes its leg, joined at border nodes. Traffic weights are per region but the overlay's boundary distances are customized from both sides. Data residency usually isn't an issue for road graphs, but probe data may be — traffic aggregation stays in-region, only aggregated speeds cross.

Why this is L6:

  • Overlap buffer handles the common case cheaply
  • Hierarchical stitching for long routes
  • Separates graph data (shareable) from probe data (residency-sensitive)

What L7 adds:

  • Region boundaries are also team boundaries for map data quality — define the border ownership contract
  • Plans for the day a region needs isolation (regulatory), keeping the global overlay optional

8. Deep Dive Scenarios#

Deep Dive 1: Rush-Hour ETA Collapse#

Context: It's 5:30pm Friday. ETA error in one metro jumped from 8% to 25% over 40 minutes. No service is erroring. On-call escalates to you.

Questions to Surface First:

  • Is traffic data fresh in that metro? (traffic_segment_age_p95)
  • Is it one metro or many? One region's aggregator or the whole pipeline?
  • Did a map release or ETA model deploy today?
  • Is there a real-world event (storm, closure) the system hasn't absorbed?

Typical L5 Approach: Look at routing service latency and errors; find nothing; check the ETA model for recent changes. Competent, but it starts where errors would be, not where staleness is.

Staff Approach: Check data age first. Found: the metro's Flink partition is stuck on a poison message; segment ages are 40 minutes. Routing kept using 4:50pm speeds. Immediate: force historical fallback for the metro (ages > 10 min should have triggered it — why didn't it?), skip the poison message to a DLQ, restart. Then fix the fallback threshold that was misconfigured at 60 minutes.

Principal Approach: Freshness is a product SLO, not a pipeline detail. Publish "traffic freshness" per region as an SLO (p95 age < 3 min, 99.5% of minutes) with an error budget; put ETA accuracy by region in the weekly business review. Build a synthetic probe canary per metro — fake trips on known roads whose speeds must appear within 2 minutes — so staleness is detected in minutes, not by users.

Staff Approach — Full Reasoning
PhaseAction
Immediate (0–5 min)Check traffic_segment_age_p95 by region; confirm stale. Force historical fallback.
TriageConsumer lag on one Flink partition; poison message (malformed probe from a new app version).
Quick fixDLQ the message; restart from checkpoint; skip backlog older than 5 min.
GuardrailsFallback threshold 10 min enforced in config validation; per-partition lag alert.
Post-mortemWhy was fallback at 60 min? Who changed it? Config review for safety thresholds.

Metrics to Watch: traffic_segment_age_p95{region}, flink_partition_lag, eta_abs_error_pct{region}, historical_fallback_ratio.

Organizational Follow-up: Safety thresholds (fallback age) require two-person review. Traffic team on-call gets region freshness dashboards.

Ownership Question: Who's paged when ETAs are wrong but nothing errors? The traffic team, on freshness — because freshness is the leading indicator and ETA error lags by 30+ minutes.

Key Takeaway: "In Maps, the dangerous outage is the one with zero errors. Page on data age."

What clears the Staff bar:

  • Checks data freshness before service health
  • Fixes the fallback that should have contained the blast
  • Converts a threshold misconfig into a review process

Deep Dive 2: The Phantom Closure#

Context: For 3 hours, routing avoided a major bridge that was open. Traffic on alternative routes doubled. Users complained about 20-minute detours.

Questions to Surface First:

  • Where did the closure come from — authority feed, user reports, or auto-detection?
  • How long do closures live without confirmation?
  • Could live probes on the bridge have contradicted the closure?

Typical L5 Approach: Remove the bad closure; add validation on the closure feed.

Staff Approach: Closures are the one live input that can set weight to infinity — they need the strongest guardrails. Found: auto-detection flagged zero flow during a 5-minute probe gap caused by a cellular outage, and closures had no expiry. Fix: auto-detected closures require ≥2 independent signals and expire in 30 minutes unless reconfirmed; any probe traversal of a "closed" segment auto-lifts the closure. Authority-fed closures keep their stated duration.

Principal Approach: Define a trust model for all live map inputs — authority feeds, user reports, auto-detection — each with confidence, expiry, and a contradiction rule, owned by one team. Report "false closure minutes" as a quality metric next to ETA error.

Staff Approach — Full Reasoning
PhaseAction
ImmediateLift the closure; confirm routes return
TriageSource: auto-detect from probe gap; no expiry
FixTwo-signal rule, 30-min expiry, contradiction auto-lift
GuardrailsAlert on closures on top-1% segments by volume
Post-mortemTrust model for live inputs

Metrics to Watch: active_closures{source}, closure_contradicted_total, closure_age_minutes, detour rate.

Organizational Follow-up: Closure sources catalogued with owners; high-impact closures on major segments page a human reviewer.

Ownership Question: Who can close a bridge in our graph? Only sources with an owner and an expiry — and a high-traffic closure pages the map-ops reviewer.

Key Takeaway: "Infinity is the most dangerous weight. Anything that sets it needs an expiry and a contradiction check."

What clears the Staff bar:

  • Treats closures as a high-risk input class
  • Uses live probes as contradiction evidence
  • Expiry by default

Deep Dive 3: Onboarding a Ride-Hailing Partner#

Context: A large ride-hailing company wants our ETA and routing API: 20K QPS peak, a 99.95% availability SLA, and ETA accuracy guarantees per city.

Questions to Surface First:

  • Is their traffic shape (airport pickups, driver repositioning) different from ours?
  • Do they need route polylines or only ETAs (matrix queries are much more expensive)?
  • What does "accuracy guarantee" mean contractually, and in which cities are we weak?
  • Will they send us probe data? Under what terms?

Typical L5 Approach: Provision 20K QPS more routing capacity and give them an API key.

Staff Approach: Clarify query shape: ride-hailing uses many-to-many ETA matrices (10 drivers × 1 rider = 10 routes per request), so 20K QPS may mean 200K route computations/sec — 2× our consumer peak. Use a matrix-optimized path (one-to-many search on the overlay). Isolate their traffic on dedicated capacity with its own quota so their surge can't degrade consumer Maps. Accuracy SLA only in cities where our measured error is already within target.

Principal Approach: This is the moment routing becomes an external product. It needs a product owner, pricing, a separate SLA tier, and legal terms on data exchange. Decide deliberately whether we want to be a routing vendor — it changes the roadmap.

Staff Approach — Full Reasoning
PhaseAction
ClarifyMatrix vs single routes; cities; SLA definition
CapacityDedicated cells, quota, matrix-optimized search
IsolationSeparate pools so partner surges can't touch consumer traffic
SLAPer-city accuracy commitments where error < target today
DataProbe exchange terms reviewed by privacy/legal

Metrics to Watch: partner_route_qps, partner_eta_error_pct{city}, partner_quota_rejections, consumer latency during partner peaks.

Organizational Follow-up: Routing API product owner; partner on-call escalation path.

Ownership Question: Who decides which cities get an accuracy SLA? The routing product owner, using the ETA team's measured error by city — never sales alone.

Key Takeaway: "A partner's '20K QPS' is a matrix. Size the computation, not the request."

What clears the Staff bar:

  • Uncovers matrix amplification
  • Isolates partner capacity
  • Refuses SLAs where data says we'd miss

Deep Dive 4: Post-Mortem — Privacy Audit Finding#

Context: An external researcher shows they can reconstruct individual commutes from our public traffic API on rural roads at 3am.

Questions to Surface First:

  • What's the minimum probe count per published segment-window, and is it enforced everywhere?
  • Are trip ends trimmed?
  • What granularity does the public API expose — segment-level per minute?

Typical L5 Approach: Raise k from 3 to 5 for public output.

Staff Approach: Found: k was enforced for internal routing weights but the public API served per-segment speeds without the threshold — and on rural roads at night, k=1 speeds leaked single trips. Fix: public outputs only publish segments meeting k ≥ 10 over a 15-minute window, coarsened to H3 res 8 in low-density areas; add noise per published value; audit job fails deploys if any published row violates thresholds.

Principal Approach: Privacy thresholds are a company standard, not per-API choices. Write the location data standard (k, retention, trimming, coarsening), put it in a shared library all location outputs must use, and give privacy engineering veto on new location-derived outputs.

Staff Approach — Full Reasoning
PhaseAction
ImmediateDisable affected public endpoint for low-density segments
Root causeThreshold enforced in one code path, not another
FixShared aggregation library; k ≥ 10 public, coarsening, noise
GuardrailsPre-publish audit; deploy gate
CommsDisclosure per legal guidance; thank researcher

Metrics to Watch: published_rows_below_k_total (must be 0), raw_probe_retention_hours, trip-end trim ratio.

Organizational Follow-up: Privacy review required for any new location-derived API.

Ownership Question: Who owns k? Privacy engineering sets it; the traffic team implements it through the shared library.

Key Takeaway: "A privacy threshold enforced in one code path is a threshold enforced in zero code paths."

What clears the Staff bar:

  • Finds the enforcement gap rather than tweaking k
  • Centralizes enforcement
  • Deploy-time audit

Deep Dive 5: Expanding to a New Country#

Context: Launch Maps with navigation in a large country with sparse official map data and few current users (so few probes).

Questions to Surface First:

  • Where does map data come from — OSM, licensed, imagery-derived, local partners?
  • Without probes, what powers traffic and ETA at launch?
  • Any residency or mapping regulations (some countries restrict map data export or precision)?

Typical L5 Approach: Import map data, deploy routing, launch.

Staff Approach: Launch with honest capability: routing on licensed + OSM data, ETA from road class and speed-limit defaults calibrated with any available trip data, no live traffic claims until probe density reaches k in major cities. Stage by city. Measure ETA error from early trips and publish internal accuracy by city before marketing claims "live traffic."

Principal Approach: Country launch is a repeatable playbook: data sourcing, legal review (map regulations), cold-start ETA, probe density thresholds for enabling features, and a regional ops team. Price the data licensing against expected users before approving.

Staff Approach — Full Reasoning
PhaseAction
DataLicense + OSM, validation, local partner corrections
LegalMap regulations, precision limits, residency
Cold startRoad-class speed defaults, no live traffic claims
Feature gatesLive traffic per city when probe density ≥ threshold
MeasureETA error by city from day 1

Metrics to Watch: probe_density_per_segment{city}, eta_abs_error_pct{city}, map-edit report rate.

Organizational Follow-up: Regional map ops team; local partner for corrections.

Ownership Question: Who decides when a city gets "live traffic" in the UI? The traffic team, gated on measured probe density and error — not the launch calendar.

Key Takeaway: "Features gate on data density. Launch dates don't create probes."

What clears the Staff bar:

  • Cold-start plan for ETA without traffic
  • Feature flags tied to data thresholds
  • Legal/regulatory as a first-class launch input

9. Level Expectations Summary#

After studying this case study, you should be able to:

  • Decompose "Design Google Maps" into tiles, search, routing, and traffic, and scope to one in under a minute
  • Explain the three clocks (weekly, per-minute, per-second) and place every component on one
  • Choose between geohash, S2, H3, quadtree, and R-tree for a stated query, and walk a radius query through an S2 covering
  • Explain why CH is fast and stale, and how partition-based customizable routing gets traffic into routes in ~2 minutes
  • Design a traffic pipeline: map matching, windows, k-anonymity, historical blend, age tagging
  • Treat map data releases like code: validation, canary, rollback, live closure overlay
  • Detect silent staleness and wrong closures with named metrics and owners
  • Frame geo as a platform with SLAs and privacy standards (L7)

The Bar for This Question#

Mid-level (L4): Stores places with lat/lng and a geohash, runs Dijkstra/A* for directions, serves tiles from a CDN. Works for a city.

Senior (L5): Covers all four subsystems competently: geohash with neighbor scan, CH or A*, averaged GPS speeds, vector tiles. Gaps: no scoping, no freshness model, no answer for traffic invalidating precomputation, no silent-failure detection.

Staff+ (L6): Scopes and commits. Uses the three-clock model. Chooses a customizable partitioned routing engine and explains the precompute/freshness trade in numbers. Designs the traffic pipeline with map matching, privacy thresholds, and age-based fallback. Treats map data as code. Names owners for data, routing, traffic, and tiles. The interviewer should learn something from the answer.


10. Staff Insiders: Controversial Opinions#

10.1 "Geohash Is a Legacy Choice"#

EvidenceImplication
Geohash cells distort with latitude and have edge-adjacency discontinuitiesEvery query must scan neighbors and over-fetch
S2 and H3 are mature, open-source, and used in major databases (MongoDB 2dsphere uses S2)The complexity argument has expired

The Staff position: Use geohash only when the store forces string prefixes. Otherwise S2 for storage, H3 for aggregation.

Why this matters in interviews: "Geohash" is the default L5 answer; knowing its failure modes is the upgrade.

10.2 "The Fastest Routing Algorithm Is the Wrong One"#

EvidenceImplication
CH queries are sub-millisecondGreat benchmark numbers
CH weights are frozen at preprocessingUseless for live traffic without hours of recompute

The Staff position: Pay 5–10× query CPU for a customizable metric. Freshness is worth more than microseconds.

Why this matters in interviews: Shows you optimize for the user outcome, not the benchmark.

10.3 "ETA Is a Prediction Product, Not a Graph Output"#

EvidenceImplication
Path time on current speeds ignores how traffic evolves during the tripLong trips systematically mispredicted
Google/DeepMind publicly applied GNNs to ETALearned correction is standard at the top end

The Staff position: ETA = time-dependent path cost + learned correction, measured against actual trip durations by region.

Why this matters in interviews: Moves ETA from "divide distance by speed" to a measured, owned model.

10.4 "Map Data Bugs Cause More Incidents Than Code Bugs"#

The Staff position: Map data changes are deploys. They get validation, canaries, and rollbacks — and a dedicated owner who is paged.

Why this matters in interviews: Demonstrates operational scars, not textbook knowledge.

10.5 "Privacy Thresholds Are Architecture"#

The Staff position: k-anonymity, trip-end trimming, and raw-probe retention change window sizes, coverage, and freshness. Decide them in the design, with privacy engineering in the room.

Why this matters in interviews: Candidates who treat privacy as a checklist item miss that it changes the data pipeline's shape.


11. The Principal Lens (L7)#

Why L7 Sees This Problem Differently#

At Staff, Maps is a product with four subsystems. At Principal, location is a company-wide asset: the road graph, traffic feed, place database, and ETA model are consumed by consumer Maps, delivery, ride-hailing partnerships, ads (store visits), and local search. Each consumer wants different freshness, accuracy, and privacy guarantees. The L7 job is to decide which of these are platforms with SLAs, which stay product-specific, and how privacy standards are enforced uniformly — because one leaky API is a company-level incident.

The Org-Level Fault Line#

Geo platform with SLAs vs per-product geo stacks.

OptionWhat WorksWhat BreaksWho Pays
Per-product geo stacksFast local iterationThree road graphs, three map-matchers, inconsistent privacy enforcementInfra budget; privacy risk; users seeing different ETAs in different apps
Central geo platform (graph, traffic, places, ETA APIs)One truth; one privacy boundary; economies of scalePlatform backlog; product teams waitPlatform headcount; product velocity
Buy (commercial map/routing vendor)Fastest; no map-data orgPer-call cost at scale; vendor controls quality and roadmapFinance; strategic dependence

The L7 default: central platform for the road graph, traffic, and privacy-enforced aggregation; product teams own their ranking, UX, and domain-specific ETA corrections (delivery prep time, pickup walking time).

Cost Model#

Assumptions: route query ~5ms CPU on customizable routing; customization cluster runs every 2 min; probes ~200 bytes; tiles CDN-served at 95%+ hit rate; cloud list prices, rough.

ScaleUsageMonthly InfraHeadcountOn-call Load
City-scale startup1M routes/day, 1 metro~$5–15K, or buy API at ~$30–150K/month equivalent at higher volumes2–4 engineers (buy routing, build ETA correction)Light; vendor handles routing
National100M routes/day, 10K probes/s~$150–400K (routing fleet, Flink, Kafka, tile CDN)20–40 (map data, routing, traffic, tiles)Per-subsystem rotations
Global1B+ routes/day, 1M+ probes/s~$3–8M (routing ~40%, traffic pipeline ~25%, tiles/CDN ~20%, storage)150+ across map data ops, platform, MLRegional follow-the-sun; freshness SLOs

Where money goes: map data acquisition and quality (people) usually exceeds infrastructure. The largest infra line is routing CPU, driven by reroute polling frequency — a product knob.

The 3-Year Evolution Path#

Diagram: The 3-Year Evolution Path

One-Way Doors vs Two-Way Doors#

DecisionDoor TypeReversibility Cost
Cell system as a storage key (S2 vs H3 vs geohash)One-way at scaleRe-keying billions of rows and every downstream join
Map data licensing terms (derived-data rights)One-wayContracts can prevent you from ever owning derived data
Raw probe retentionOne-way once collected (privacy/regulatory)Deletion is easy; un-collecting a breach is impossible
Routing algorithm (CH vs customizable)Two-way with effort1–2 quarters with shadow traffic
Traffic window size, k thresholdTwo-wayConfig (after privacy review)
Tile format (raster vs vector)Two-way-ishClient support lags 12–18 months
Build vs buy routingTwo-way with an abstraction layerPainful without one

The Standard I'd Write#

RFC: Location Data & Geo Services Standard (v1)

Scope: Any service that stores, aggregates, or publishes data derived from user location.

MUST:

  • Use S2 cell IDs (storage) or H3 (aggregation) via the shared geo library — no bespoke grids.
  • Aggregate location-derived outputs through the privacy library: k ≥ 5 internal, k ≥ 10 external; trip ends trimmed ≥200m.
  • Retain raw probes ≤24h unless an approved exception exists.
  • Tag every derived value with source and age; consumers MUST handle stale values.
  • Ship map data changes through validated, canaried releases with instant rollback.

SHOULD:

  • Consume routing and ETA via platform APIs rather than embedding engines.
  • Report ETA accuracy by region to the platform dashboard.

Exceptions: Privacy engineering + geo platform council; time-bounded.

Success metrics: 0 published rows below k; traffic freshness p95 < 3 min in 99.5% of minutes; ETA ±10% for 90% of trips in top-100 metros; one road graph company-wide.

What I'd Tell the VP#

"Location data powers five of our products, but today three teams maintain their own maps and routing, which costs us about 25 engineers of duplicated effort and gives customers different arrival times in different apps. I'm proposing one geo platform that owns the road map, live traffic, and arrival-time predictions, with privacy rules built in so no team can accidentally expose where people live. It takes about a year and pays back in the second year through consolidation. The biggest risk we're addressing is silent wrongness — routes based on stale data — which we'll now measure and alert on like an outage."

Principal Interview Signals#

SignalWhat It Sounds Like
Platform framing"The road graph and traffic feed are company assets with SLAs; ranking and domain ETA stay with products."
Pricing freshness"Rerouting every 30s vs 2 min is a ~3× routing-fleet cost difference — that's a product decision."
One-way doors"The cell system is a storage key — pick it once, org-wide."
Privacy as org standard"k and retention live in a shared library with a deploy-time audit, not in each team's code."
Build vs buy over time"Buy routing now, build when the bill passes twice a team's cost, and keep an abstraction so we can switch."

Staff answers that L7 interviewers find insufficient:

  • "The traffic team owns freshness" — true for Maps, but ignores the three other consumers who each re-derive traffic.
  • "We use k ≥ 5" — a number, not an enforced company standard.
  • "We'll canary map releases" — for one product; not a release governance model across many data sources.

🧭 Principal Move: "Before we optimize our routing engine, let's decide who else needs it. If delivery and ads will consume ETAs within a year, the right unit of design is a geo platform with a privacy boundary, and consumer Maps is its first customer."


Appendices

Appendix A: Spatial Indexing in Depth#

A.1 Geohash#

encode(lat, lng, precision):
  bits = interleave(bisect(lng, -180..180), bisect(lat, -90..90))   # lng first
  return base32(bits)[0:precision]       # 5 bits per char

radius_query(lat, lng, r):
  p = precision_for(r)                   # cell ≥ r in both dims
  cells = [cell(lat,lng,p)] + neighbors8(cell)
  return [x for c in cells for x in prefix_scan(c) if haversine(x) <= r]

Why it's wrong at edges: the Z-order curve jumps; adjacent points on opposite sides of a major boundary share no prefix. Neighbor scans fix correctness at the cost of 9× scans.

A.2 S2#

Cube-face projection → (face, i, j) → Hilbert position → 64-bit ID with level encoded in trailing bits. Children of a cell occupy a contiguous ID range: [cell.range_min(), cell.range_max()]. Region coverer parameters: min_level, max_level, max_cells — trade precision (fewer false positives) against number of ranges.

A.3 H3#

Icosahedron-based hexagonal grid, 16 resolutions, aperture 7 (each parent ≈ 7 children). k_ring(cell, k) gives all cells within k steps — ideal for smoothing traffic speeds or surge. Not exactly hierarchical; don't use for exact containment.

A.4 Quadtree for Moving Objects#

In-memory, split leaf at >N points (e.g., 100), merge below N/4. Good for drivers updating every 4s (Proximity Matching); not for durable storage.

A.5 Quick Comparison#

IndexCell ShapeUniformityQuery PrimitiveBest Store
GeohashRectanglePoor at high latitudePrefix scan + 8 neighborsRedis, DynamoDB, any sorted KV
S2Quad on cubeGoodCovering → ID rangesBigtable/Spanner/Cassandra-style sorted KV
H3HexagonGoodk-ring, polyfillWarehouse, stream aggregations
QuadtreeAdaptive squareAdapts to densityTree traversalIn-memory service
R-treeBounding boxesN/ABox intersectionPostGIS
Diagram: A.5 Quick Comparison

Appendix B: Routing Mechanics#

B.1 Contraction Hierarchies#

preprocess:
  order nodes by importance (edge difference, contracted neighbors, ...)
  for v in order:
    for each pair (u, w) of uncontracted neighbors via v:
      if shortest u→w goes through v: add shortcut(u, w, d(u,v)+d(v,w))
query(s, t):
  bidirectional Dijkstra, forward only to higher-ranked nodes, backward likewise
  meet at the highest node on the path; unpack shortcuts for the polyline

B.2 Partition-Based Customizable Routing#

preprocess (weekly): multi-level balanced partition; each level's cells have few boundary nodes
customize (every 1–2 min), per cell, bottom-up, in parallel:
  for each pair of boundary nodes (a, b): overlay_edge(a, b) = shortest path inside cell
query: bidirectional Dijkstra using fine edges near s and t, overlay edges elsewhere

B.3 Time-Dependent Routing#

For trips >45 minutes, edge weight = f(arrival time at edge). Use live speeds for edges reached within ~15–20 minutes, then blend toward historical profiles for the predicted arrival bucket.

B.4 Reroute Policy#

Active sessions re-query every 1–2 min or when a traffic change touches segments on the current route (push invalidation by segment → session index). Offer reroute when savings ≥ max(2 min, 10%) to avoid flapping.

Appendix C: Traffic Pipeline#

Diagram: Appendix C: Traffic Pipeline

Key rules: trim trip ends; rotate device IDs daily; drop raw probes after 24h; blend w = min(1, probe_count / 20) × freshness_decay(age).

Appendix D: API Contract and Client Behavior#

  • Route responses include map_release, traffic_age_s, eta_confidence for reproducibility.
  • Clients batch probes (5–15s batches) to save battery and connections; server controls sampling rate by config.
  • Tile URLs: /tiles/{release}/{z}/{x}/{y}.mvt — immutable, Cache-Control: max-age=31536000.
  • Retry with jitter on route failures; show cached last route if routing is unavailable during navigation.

Appendix E: Observability#

E.1 Core Metrics#

route_latency_ms{p50,p99}
traffic_segment_age_seconds{p50,p95}{region}
historical_fallback_ratio{region}
eta_abs_error_pct{region}          # from completed trips
reroute_rate, off_route_rate {region, map_release}
mapmatch_consumer_lag
published_rows_below_k_total       # must be 0
cdn_tile_hit_rate

E.2 Critical Alerts#

AlertThresholdAction
Traffic stalenessp95 age >5 min for 5 minPage traffic
ETA error>1.5× baseline in a region for 30 minPage ETA/ML
Reroute spike>3× after a releaseAuto-halt rollout; page map data
Privacyany row below kSEV1; stop publishing
Route latencyp99 >200msPage routing

E.3 Debugging the Silent Failure#

Start with data age by region, then release version, then model version. Errors are the last place to look.

Appendix F: Scale Evolution#

ScaleWhat WorksWhat Changes
One cityPostGIS + A*, buy trafficNothing clever
One countryOpen-source engine with CH, own traffic pipelineMap matching, releases
ContinentCustomizable routing, regional graphsFreshness SLOs, canaries
GlobalRegional graphs + overlay, geo platformPrivacy standard, partner APIs

What You Don't Build on Day One#

  • Your own routing engine (buy or open source)
  • ML ETA models before you have trip-duration ground truth
  • Global graph stitching before cross-region demand exists
  • Raster tiles in any form

Appendix G: Multi-Tenancy and Cost#

  • Partner isolation: dedicated routing pools and quotas for API customers.
  • Matrix pricing: bill per element, not per request (a 25×25 matrix is 625 routes).
  • Reroute frequency is the biggest product-controlled cost lever; expose it in cost dashboards.
  1. Loading the index…