← System design

Search & Discovery

Design Google Maps

A maps product is really three systems wearing one interface. One draws the planet from tiles that barely change, one answers shortest-path questions over a graph with a billion edges in a few milliseconds, and one listens to millions of moving phones to work out how fast every road is right now. Each has a different bottleneck, and the interview is about keeping them apart while letting traffic data flow into routing within minutes.
Core: precomputed road hierarchy + live traffic weights3.7M GPS pings a secondTiles served almost entirely from CDNRoute answer in under 300 ms

01

What we are building

01

What we are building

Someone opens the app in a new city, pans around, searches for a cafe, taps "directions" and starts driving. In that minute the system has served a few dozen map tiles, resolved a free-text query to a place, computed a route across tens of thousands of road segments, predicted an arrival time that accounts for the traffic jam two kilometres ahead, and started tracking the phone so it can reroute if the driver misses a turn.

Behind that flow sit a map rendering system (base map data turned into tiles), a search and geocoding system (text to coordinates and back), a routing engine (paths and travel times over the road graph) and a traffic system (GPS probes turned into live road speeds). The phones that use the product also feed it: every navigating device is a traffic sensor.

Where to spend interview time

Tiles and search are mostly a caching and indexing story. The interesting depth is in routing at scale and keeping it fresh with live traffic, because the fastest shortest-path techniques depend on heavy preprocessing, and traffic changes the edge weights that preprocessing was built on.

02

Scope and targets

02

Scope and targets

Functional
  • Render an interactive map at any zoom level, with roads, labels, buildings and points of interest.
  • Search for places and addresses, and reverse geocode a tapped point to an address.
  • Compute driving, walking and cycling routes between two or more points, with two or three alternatives.
  • Show an ETA that reflects current traffic, and colour roads by congestion.
  • Turn-by-turn navigation that detects a missed turn and reroutes automatically.
  • Collect anonymised location pings from navigating devices to estimate traffic.
Non-Functional
  • Route responses under 300 ms p95 for trips up to 1,000 km.
  • Map panning feels instant: tiles mostly from device cache or a nearby CDN edge.
  • Live traffic visible in routing within about two minutes of a slowdown.
  • ETA error under 10% for the large majority of trips.
  • Navigation keeps working through spotty mobile coverage.
  • Location data is anonymised and trimmed at both ends of a trip to protect home addresses.
Pin these down early

Ask which travel modes matter, whether public transit is in scope (it needs a timetable-based algorithm, not road graph search), whether the map must work offline, and how fresh traffic must be. In this walkthrough we cover roads and walking, assume an online client with a tile cache, and treat transit as out of scope.

03

Sizing the three workloads

03

Sizing the three workloads

Assume 300 million daily users, an average of three map sessions each, and up to 15 million devices navigating at the evening peak. Navigating devices send a location fix every four seconds.

QuantityWorkingResult
Tile requests per day300M x 3 sessions x 40 tiles not in device cacheabout 36B
Peak tile rate36B / 86,400 x 3 for peak hourabout 1.25M / s
Tile rate reaching originat a 96% CDN hit rateabout 50K / s
Route requests per day300M x 0.6 plus reroutesabout 250M
ETA refreshes at peak15M navigating / one check per 60 sabout 250K / s
GPS pings at peak15M / 4 sabout 3.7M / s
Probe ingest bandwidth3.7M x about 48 B per pingabout 180 MB / s
Road graphroutable intersections and directed segments, worldabout 450M nodes, 1.1B edges
Graph memory per replicaedges, shortcuts, geometry ids, about 40 B per edge after preprocessingabout 90 GB
Vector tile setland tiles to zoom 16, about 15 KB averageabout 20 TB per map release
Tiles are the biggest number and the easiest one

Over a million tile fetches a second sounds alarming until you notice that tiles are immutable per map release. Give them versioned URLs, cache them for days, and the CDN does almost all of the work.

Routing is the expensive compute

A quarter of a million ETA refreshes a second on a graph with a billion edges is impossible with plain Dijkstra, which can visit millions of nodes for one long trip. The routing engine must answer a query by touching a few thousand nodes, not a few million. That is what the deep dive solves.

04

API design

04

API design

GET/tiles/v{release}/{z}/{x}/{y}.mvtFetch one vector tile
// Served by the CDN; the release number in the path makes the URL immutable Cache-Control: public, max-age=604800, immutable Content-Type: application/vnd.mapbox-vector-tile // Traffic colouring is a separate overlay tile with a 60 second max-age
GET/v1/places/search?q=flat+white&near=12.9716,77.5946&limit=10Place and address search
{ "results": [ { "place_id": "pl_93kd", "name": "Third Wave Coffee", "lat": 12.9731, "lng": 77.6012, "distance_m": 740 } ] }
POST/v1/routesCompute routes with live ETAs
{ "origin": { "lat": 12.9352, "lng": 77.6245 }, "destination": { "place_id": "pl_93kd" }, "mode": "drive", "depart_at": "now", "avoid": ["tolls"], "alternatives": 2 } // 200 OK { "routes": [ { "route_id": "r_1a", "distance_m": 6820, "eta_s": 1310, "traffic_delay_s": 280, "polyline": "o~|mAqb{xM...", "steps": [ /* manoeuvres */ ] } ], "weights_version": "blr-2026-10-01T18:04Z" }
POST/v1/nav/{session_id}/pingsBatched location fixes from a navigating device
{ "fixes": [ { "t": 1759327441, "lat": 12.94012, "lng": 77.62877, "speed": 6.1, "heading": 71, "acc_m": 8 } ] } // Response may carry a new route if the device left the old one or a faster path appeared
Design note

The session id ties pings to a navigation session, not to an account. The probe pipeline only ever sees a rotating anonymous trip id, and the first and last few hundred metres of every trip are dropped before aggregation.

05

System overview

05

System overview

Tiles, routing and the traffic loopDiagram
flowchart LR
	APP["Mobile or web client"] --> CDN["CDN edge"]
	CDN -->|miss| TS["Tile origin"]
	TS --> OBJ[("Tile object store")]
	APP --> GW["API gateway"]
	GW --> SR["Search and geocoding"]
	GW --> RT["Routing service, graph in memory"]
	GW --> NAV["Navigation sessions"]
	NAV --> RT
	NAV -. "pings" .-> K[["Probe stream"]]
	K --> MM["Map matcher"]
	MM --> AGG["Segment speed aggregator"]
	AGG --> SP[("Live speed store")]
	SP --> CUS["Weight customizer"]
	CUS -. "new weights every 2 min" .-> RT

Two offline pipelines feed the online services. The map build pipeline takes base map data (roads, buildings, labels, places), produces a numbered release, renders vector tiles into object storage, builds the search index and runs the expensive routing preprocessing. A release is published once and served unchanged until the next one, typically daily for corrections and weekly for larger updates.

The traffic loop runs continuously. Navigation sessions push pings into a partitioned stream keyed by geographic cell, so all pings for one area land on the same consumers. Map matchers snap noisy GPS points to road segments. Aggregators compute a current speed per segment per two-minute window and write it to a live speed store. Every couple of minutes, a customizer turns those speeds into new edge weights for each region and hands them to the routing servers, which swap them in atomically.

Online, the client talks to three services. Tiles come straight from the CDN. Search and geocoding answer from their own index. The routing service holds the preprocessed graph for its region in memory and answers queries using whatever weight version is current, returning that version with the route so ETAs can be traced back to the data they used.

Tile serving

Vector tiles carry geometry and attributes; the client styles and draws them. One tile set supports light mode, dark mode and every language label, and the release-versioned URL means CDN caches never need purging.

Search and geocoding

A text index over places and addresses, combined with a spatial filter on S2 or geohash cells near the user, ranked by text match, distance and popularity. Reverse geocoding is a nearest-feature lookup in the same cells.

Routing service

Stateless across requests but holds 10 to 90 GB of graph per process, depending on whether it serves one region or the world. Long trips that cross regions use a coarse overlay graph to stitch regions together.

Navigation sessions

Track where each device is on its route, detect deviations, ask routing for a fresh ETA once a minute, and push a reroute when a clearly faster path appears.

Traffic pipeline

A streaming job over the probe stream. It matches, aggregates and smooths, and falls back to historical speed profiles for segments with too few recent probes.

Weight customizer

Combines live speeds, historical profiles, closures and incident reports into one weight per edge, then reruns the fast customization step of the routing preprocessing.

06

Deep dive: fast routes on a graph that keeps changing

06

Deep dive: fast routes on a graph that keeps changing

Why plain search is not enough

Dijkstra explores outward in rings of equal cost. For a 400 km drive it settles a large share of the road network between the two cities, easily several million nodes and a second or more of CPU. A* with a straight-line heuristic helps, but on real road networks the heuristic is weak and the speedup is small. At 250,000 queries a second you need a thousand times less work per query, and that only comes from preprocessing.

Contraction hierarchies in two steps

Preprocessing. Rank every node by importance, roughly how many shortest paths would need it. Then remove nodes one by one from least to most important. When you remove a node v, look at each pair of neighbours u and w. If the only shortest path from u to w goes through v, add a shortcut edge u to w whose weight is the sum of the two edges it replaces, and remember that it stands for u, v, w. A quiet cul-de-sac is removed early and creates no shortcuts. A motorway junction is removed late and inherits shortcuts that span hundreds of kilometres. The result is the original graph plus a set of shortcuts, typically about as many as the original edges.

Query. Run Dijkstra forward from the origin and backward from the destination at the same time, but only ever follow edges that lead to a more important node. Both searches climb quickly from local streets to arterials to motorways and meet near the top of the hierarchy. The best meeting point gives the shortest path. Each search settles only a few hundred to a few thousand nodes, so a continental query takes around a millisecond.

Unpacking. The answer is a path made partly of shortcuts. Expand each shortcut recursively into the two edges it replaced until only original road segments remain. That gives the geometry for the polyline and the list of turns for navigation instructions.

Bidirectional upward searchDiagram
flowchart LR
	O["Origin on a side street"] --> A1["Local arterial"]
	A1 --> H1["Ring road junction"]
	H1 --> M["Motorway hub, highest rank met by both"]
	D["Destination on a side street"] --> A2["Local arterial"]
	A2 --> H2["Motorway exit"]
	H2 --> M
	M -. "unpack shortcuts" .-> P["Full turn-by-turn path"]

The catch: traffic changes the weights

Classic contraction hierarchies bake the edge weights into the node order and the shortcuts. When a motorway slows from 100 km/h to 20 km/h, the shortcut weights are wrong, and some shortcuts may no longer represent shortest paths at all. Rebuilding the hierarchy for a country takes minutes to hours of CPU, which is far too slow for traffic that changes every few minutes.

The fix used in production engines is to split preprocessing into two phases. The metric-independent phase depends only on the shape of the road network: it computes a node order or a multi-level partition of the graph, which is expensive but only reruns when the map itself changes. The customization phase takes a set of edge weights and computes the weights of every shortcut or every cell boundary clique bottom-up. It is mostly arithmetic over arrays, runs in parallel, and finishes in seconds for a large country. Customizable Route Planning, the approach Microsoft described for Bing Maps, and customizable contraction hierarchies both follow this split.

So the traffic loop does not rebuild anything. Every two minutes the customizer produces fresh edge weights, runs customization for each region, and ships the new weight arrays to the routing servers. Each server loads them next to the old arrays and flips a pointer. Requests already in flight finish on the old version, and new requests use the new one.

From probes to edge weights

Raw GPS points are off by five to twenty metres and arrive irregularly. A map matcher treats the trip as a hidden Markov model: candidate road segments near each fix are the states, and the transitions favour routes that are physically drivable between consecutive fixes. The output is a list of segments with entry and exit times, from which you get a traversal speed per segment.

The aggregator groups traversals by segment and two-minute window and takes a median, not a mean, because a bus stopping, a driver parking or a delivery rider on a footpath produces outliers that would drag a mean down. A segment with fewer than three traversals in the window blends the live value with its historical profile for that weekday and time, weighting history more heavily as probes get sparse.

ETA is not just the sum of edge weights. Turns, traffic lights and the delay to join a congested road add time, and conditions will change during a long trip. A learned correction model trained on completed trips adjusts the graph estimate, and for trips longer than about 30 minutes later segments use predicted speeds for the time you will actually reach them, not the speed right now.

The full picture in one sentence

An expensive, rarely rerun ordering of the road network, a cheap customization rerun every two minutes from live speeds, and a millisecond bidirectional search on top: that is how routing stays both fast and traffic aware.

07

Choices and their costs

07

Choices and their costs

Chosen
Vector tiles styled on the device

Smaller downloads, sharp at any zoom and rotation, one tile set for every theme and language, and labels that stay upright as the map turns. The cost is GPU and battery on the client and a heavier rendering engine to maintain.

Default for apps
Kept for niche uses
Pre-rendered raster tiles

Trivial to display anywhere, including static map images in emails and very old devices. Every style and language multiplies storage, and a rotated map shows sideways text.

Static images only
Chosen
Customizable hierarchy or partition routing

Millisecond queries with traffic weights refreshed every two minutes. You pay with a complex offline pipeline and with memory for shortcuts or cell data on every routing server.

Fast and fresh
Rejected
A* over the raw graph with live weights

No preprocessing and always uses the newest speeds. Fine for short city trips, but long queries explore millions of nodes, and costs explode at peak load.

Too slow at scale
Chosen
Regional graph shards plus an overlay

Each routing server loads one region, say a country or a group of states, which keeps memory per process modest and lets busy regions scale independently. Cross-region trips use a small overlay of boundary nodes.

Scales by geography
Alternative
Whole-world graph on every server

Simplest query logic, any server can answer any trip. Every replica needs around 90 GB of RAM and every weight update ships the whole world, which wastes most of it on regions that are asleep.

Workable, costly
Chosen
Pre-generate low zooms, build high zooms on demand

Zoom levels up to about 12 cover the whole planet in a few million tiles and are built with each release. Deeper zooms are generated on first request and cached, since most of the world's ocean and desert is never viewed up close.

Bounded build cost
Alternative
Pre-generate every zoom level

Predictable latency for every tile, at the cost of building and storing billions of tiles that nobody looks at. Release builds get slow, which delays map corrections.

Only for small areas
Chosen
Median speed per window, blended with history

Robust to stopped buses, parked cars and cyclists in the probe data, and stable when only a few probes exist. A sudden real slowdown takes one or two windows to show fully.

Robust estimate
Rejected
Mean of the latest probes

Reacts fastest and is easy to compute incrementally, but one slow outlier on a lightly travelled road turns it red, and routes swing away from roads that are actually clear.

Too noisy

08

When things break

08

When things break

🚧
The traffic pipeline stalls

A consumer group falls behind after a bad deploy. Live speeds stop updating, and routes keep using a jam that cleared twenty minutes ago.

Every weight carries a timestamp. When live data for a segment is older than a few windows, the customizer falls back to historical profiles automatically. Alert on stream lag, not on user complaints.
🧩
A bad map release ships a broken road

An edit marks a motorway as one-way in the wrong direction. Routing sends thousands of drivers on detours, or worse, tries to send them the wrong way.

Run route regression suites on each release: compare thousands of known trips against the previous release and block publication when distances or ETAs jump. Keep the previous release loaded so a rollback is one config change.
👻
Spoofed or skewed probe data

A group of phones crawling down a street, or a GPS spoofing app, makes a clear road look jammed and diverts traffic around it.

Require a minimum number of distinct devices per segment and window, weight devices by their history, and reject traces that are not physically plausible. Treat incident reports from a single source as hints until confirmed.
🔥
Routing servers run out of memory on a weight swap

Loading new weight arrays next to the old ones doubles memory for a moment. If every server swaps at the same instant, some are killed and the fleet loses capacity during peak.

Size servers for two weight versions, stagger swaps across the fleet, and keep the graph structure shared so only the weight arrays are duplicated.
📶
A driver enters a tunnel or loses signal

Pings stop, the server cannot tell whether the driver deviated, and the client has no fresh route if it misses a turn.

The client keeps the full route and a corridor of tiles around it, dead-reckons position from speed and heading, and can compute a short local reroute on the device. The server reconciles when the connection returns.
🌧️
Everyone gets the same detour

A crash closes a motorway. Every navigation session reroutes onto the same side road, which jams within minutes.

Spread reroutes across good alternatives instead of always picking the single best one, and let live speeds on the detour feed back quickly into weights.

09

Interview notes

09

Interview notes

1

Split the problem into three on purpose. Say early that tiles, routing and traffic are separate systems with different scaling problems, then choose which to go deep on.

2

Explain why Dijkstra fails, with a number. Millions of nodes per long query at hundreds of thousands of queries a second is the argument for preprocessing.

3

Know one speedup technique well. Contraction hierarchies are the easiest to explain: shortcuts, rank, upward search from both ends, unpack.

4

Raise the traffic conflict yourself. Preprocessing assumes fixed weights and traffic breaks that assumption. Separating structure from customization is the answer strong candidates give.

5

Describe the probe pipeline end to end. Ping, map match, aggregate per window, blend with history, publish weights. Mention privacy trimming at trip ends.

6

Make tiles boring. Versioned immutable URLs, CDN, vector format. Spending ten minutes on tile caching usually means less time on the part being graded.

7

Separate ETA from route cost. Turns, lights and future traffic make ETA its own prediction problem, often corrected by a model trained on finished trips.

8

Mention offline behaviour. Tunnels and rural roads are normal. A cached route corridor and on-device rerouting show you have thought about real driving.

10

Growing the design in stages

11

Growing the design in stages

Stage 1: one city
Raster tiles and Dijkstra

Pre-render tiles for one metro area, keep the road graph for that city in memory and run A* per request. With a few hundred thousand edges this is fast enough and simple.

Stage 2: a country
Vector tiles, CDN and contraction hierarchies

Long trips make plain search slow, so preprocess the graph. Move to vector tiles with release-versioned URLs behind a CDN. Traffic still comes from historical averages.

Stage 3: live traffic
Probe stream and periodic customization

Collect pings from navigating users, build the map matching and aggregation pipeline, and move routing to a customizable scheme so live weights apply every few minutes.

Stage 4: many countries
Regional shards and an overlay graph

Partition routing by region, scale each region by its own load, and stitch cross-border trips through boundary nodes. Search gets regional indexes and local language support.

Stage 5: the whole planet
Predictive ETAs and offline maps

Train ETA models on completed trips, predict speeds for the time each segment will be reached, and let users download regions for offline navigation with on-device routing.

Watch and read

Try next

Free to read · better with landed

Whiteboard this with landed

landed runs it as a live system design round on the whiteboard and grades your trade-offs.