Vibe Engines
YouTube

Vibe Engines is free, interactive AI engineering & system design — runs in your browser. Pick a learning path

System Design

Design Google Maps

Step 1 / 9

Learn system design by building a mapping and navigation service like Google Maps step by step.

The numbers to beatz/x/ytile addressCDNedge cachedviewportonly fetched

The whole design, in writing

Every step of the build above, written out: the problem each piece solves, the option that was taken and the ones that were not, the numbers, and how it fails in production.

The big idea

What is Google Maps?

Render the whole world as a smooth, zoomable map, and compute the fastest route between any two points — accounting for live traffic — in well under a second, for billions of users. Two very different problems live here: drawing the map and routing over it.

Userpan · route
New in this step: User.

Pre-render the map into cacheable tiles served from the edge, and model roads as a graph you run shortest-path search over. Make routing fast with precomputation, fold in live traffic for ETAs, and add geocoding to connect names to places.

What the new pieces do

Userclient
Pans and zooms the map, searches places, and asks for directions with a live ETA — all expected to feel instant while moving.

Step 1 · Draw the world

Map tiles

You can’t send the entire planet’s map to a phone, nor re-render it per request. Yet panning and zooming must feel instant at every zoom level, everywhere.

Maps APITile ServiceTile Store + CDN
New in this step: Maps API, Tile Service, Tile Store + CDN. · swipe to pan the diagram

You can’t send the planet’s map to a phone or re-render it per request. How do you make pan/zoom feel instant everywhere?

  1. Rendering on demand for billions of pans/zooms is enormously expensive and barely cacheable since every viewport differs. Precompute reusable pieces instead.

  2. Small tiles addressed by zoom/x/y let the client fetch only what’s in view; tiles rarely change, so they edge-cache beautifully and load fast worldwide. "Render the world" becomes "fetch a few static images."

  3. The planet’s map data is far too large to download to a phone, and would be stale immediately. The client should request just the tiles in its viewport, rendered ahead of time.

Pre-render the map into tiles — small square images (or vector tiles) addressed by zoom / x / y. The client requests only the tiles in view; a Tile Service serves them from a CDN-fronted store. Since tiles rarely change, they cache beautifully.

Why this piece earns its place

The assumption hiding in “tiles rarely change” is worth saying out loud, because the day a road opens, a tile that edges have been happily serving for years is wrong. I wouldn’t reach for a purge: purging a planet of caches is slow, and a purge that half-lands leaves tiles from two different renders butted up against each other, which on screen looks like the map has torn along a seam. I’d put the render version in the tile path instead, so every tile stays immutable forever, a re-render publishes new paths rather than overwriting old ones, and a client that picks a version at session start sees one self-consistent world all the way through. The second assumption is that you can materialize every tile at every zoom. You can’t — the grid subdivides with each level and most of it is ocean, desert and farmland nobody will ever look at. Pre-render the zooms and the places that actually get traffic, generate the long tail on first request, and let the edge cache it like any other tile.

  • z/x/ytile address
  • CDNedge cached
  • viewportonly fetched

What the new pieces do

Maps APIbackend
Routes the three core requests: map tiles to render, route computations, and place/address lookups. The front door to every map feature.
Tile Servicecache
Serves the little square images (or vector tiles) that tile together into the map, addressed by zoom level and x/y grid coordinates.
Tile Store + CDNstore
Pre-rendered tiles in cheap storage, served from a CDN. Tiles change rarely, so they cache at the edge and load fast worldwide.

Step 2 · Roads as math

The road graph

Directions aren’t about images — they’re about connectivity. To compute a route you need a structure that captures which roads connect to which, and how “costly” each segment is.

Maps APIRoad Graph
New in this step: Road Graph. · swipe to pan the diagram

Directions aren’t about images — they’re about connectivity. What structure do you compute routes over?

  1. Tiles are pixels for looking at, not connectivity for computing over — you can’t route on images. Drawing and routing are different problems needing different structures.

  2. A flat list of roads doesn’t capture which connects to which, turn restrictions, or costs — everything routing needs. You need explicit connectivity with weights.

  3. Nodes are intersections, edges are road segments weighted by length and typical speed (plus one-ways and turn restrictions). Routing becomes shortest-path over this graph — separate from the visual tiles.

Model the map as a graph: intersections are nodes, road segments are edges weighted by length and typical speed (plus one-ways and turn restrictions). Routing becomes a shortest-path problem over this graph.

Why this piece earns its place

What this model leaves out is the part that produces embarrassing directions. Nodes and edges express “this road meets that one,” but a route isn’t really a sequence of edges — it’s a sequence of manoeuvres, and a lot of the cost lives in the manoeuvre rather than in the road. Turning left across oncoming traffic at a signal costs far more than continuing straight through the same intersection, and some of those turns are simply illegal, or illegal at certain hours. A plain node-and-edge graph has nowhere to put that: the cost of reaching a node is the same no matter which way you leave it. So in practice you either hang a turn table off each node — a cost for every in-edge/out-edge pair, with a forbidden marker for the restrictions — or you flip to an edge-based graph, where segments become the nodes and turns become the edges, which buys you more nodes but makes turns first-class. Skip it and the router will confidently send someone across three lanes they legally cannot cross.

What the new pieces do

Road Graphstore
The map as a graph: intersections are nodes, road segments are weighted edges (by length/speed). All routing is graph search over this.

Step 3 · Find the way

Shortest-path routing

Given start and end nodes, you need the lowest-cost path through a graph with hundreds of millions of edges — and a plain Dijkstra exploration would crawl outward over an enormous area.

Maps APIRouting EngineRoad Graph
New in this step: Routing Engine. · swipe to pan the diagram

Find the lowest-cost path in a graph with hundreds of millions of edges. Plain Dijkstra crawls outward everywhere. Better?

  1. Enumerating all paths is exponential — infeasible on a continental graph. You need a guided search that provably finds the optimum without exploring everything.

  2. The heuristic (as-the-crow-flies distance to the goal) steers the search toward the destination instead of expanding in all directions — far fewer nodes explored, same optimal path.

  3. Pure greedy best-first isn’t optimal — it can charge toward the goal down a dead-end or slow road. A* combines goal-direction WITH actual cost-so-far to stay optimal.

A Routing Engine runs A*: Dijkstra guided by a heuristic (straight-line distance to the goal) so the search heads toward the destination instead of expanding in all directions. Far fewer nodes explored, same optimal path.

Why this piece earns its place

The heuristic is the knob here, and the first thing to get right is that it has to be in the same currency as the edge weights. If edges are weighted by travel time, a straight-line distance isn’t comparable to them at all — you divide that distance by the fastest speed anywhere on the network to turn it into a lower bound on time. That conversion is exactly what keeps the heuristic admissible, never overestimating, which is the property that lets A* still return the best route rather than merely a good one. And the divisor is the lever. Use the true network maximum and the bound is safe but weak: on city streets the estimate sits far under the real travel time, the search barely narrows, and you’ve paid for A* while getting close to Dijkstra. Scale the heuristic up and the explored frontier tightens sharply and queries get much faster, but the guarantee is gone and the route returned can be slightly worse than optimal. That’s a trade some production systems take deliberately; what matters is knowing which side you’re on rather than landing there by accident.

What the new pieces do

Routing Engineservice
Computes the best route between two points over the road graph, accounting for road types, turn restrictions and, later, live traffic.

Back of the envelope

A* = Dijkstra + heuristic
straight-line distance to the goal
heuristic steers toward destination
a fraction of the nodes explored
admissible ⇒ still optimal
never overestimates real distance

Step 4 · Make it instant

Precompute for speed

Even A* is too slow for continental routes at query time, and you serve millions of route requests per second. Exploring the raw graph on every request simply won’t hit sub-second latency.

UserMaps APITile ServiceTile Store + CDNRouting EngineRoad Graph
The system as it stands at this step. · swipe to pan the diagram

Even A* is too slow for continental routes at millions of queries/sec. How do you get millisecond long-distance routes?

  1. More machines lower cost-per-query but each continental A* is still slow — you can’t parallelize one path search enough for sub-second. Do less work per query, not more hardware.

  2. There are astronomically many origin-destination pairs — precomputing all routes is impossible to store. You precompute reusable shortcuts, not full answers.

  3. Heavy offline preprocessing builds "highway" shortcut edges; online queries hop over them, skipping most local roads. Long routes become a few hops at the top of a hierarchy — milliseconds. The offline/online split again.

Precompute shortcuts with contraction hierarchies (or highway/transit hierarchies): collapse the graph so long routes hop over precomputed “highways” instead of every local road. Queries then run in milliseconds by skipping most of the graph.

Why this piece earns its place

What this bakes in is that the weights were already known when the shortcuts were built. Every shortcut stands for a chain of real segments at a cost frozen at contraction time, so anything that changes the cost underneath one makes that shortcut lie — and it lies quietly, because the query still returns a path and the path still looks plausible. That drives a rule I’d hold to: never express a change as an edit to the graph. A closed road is tempting to simply delete, and deleting it leaves every shortcut built over it intact and still advertising a route through a road that isn’t there. Closures go in as weights — make the segment expensive enough that nothing sane routes over it — so the structure stays valid and only numbers move. Which points at the real split: treat the topology work, the contraction order and the shortcut set, as the expensive thing you rebuild rarely, and keep the costs as a thin layer over it you can recompute cheaply and often. A genuinely new road does need the heavy rebuild, and that runs region by region on its own schedule.

  • offlineshortcuts built
  • msroute query
  • continentalin real time

Back of the envelope

offline: build shortcut "highways"
heavy preprocessing, done once
online: a few hops at the top
skip most local roads
continental route ⇒ ms
trade build time for query time

Step 5 · Account for traffic

Live ETAs

The shortest route by distance isn’t the fastest in a jam, and a static ETA is wrong the moment congestion changes. You need to know current speeds on every road.

ETA Servicetraffic-awareTraffic Datalive speedsLocation PingsGPS stream
New in this step: ETA Service, Traffic Data, Location Pings.

The shortest route by distance isn’t fastest in a jam, and you need CURRENT speeds on every road. Where do they come from?

  1. Physical sensors on every road on earth are impossibly expensive and sparse. You already have hundreds of millions of moving sensors — the users’ phones.

  2. Every moving user is a sensor; aggregating anonymized location pings gives live speed per segment. The ETA service weights routes by these (plus historical patterns), and routing re-weights edges to avoid jams.

  3. Time-of-day priors help (and are a fallback), but can’t see today’s accident or event-driven jam. Real ETAs need live ground truth, which the crowd of moving users provides.

Phones stream anonymized Location Pings; aggregating them gives live Traffic Data — current speed per segment. The ETA Service weights the route by these speeds (and historical patterns) to estimate arrival, and routing re-weights edges to avoid jams.

Why this piece earns its place

Two things bite once the traffic layer starts closing the loop on itself. The first is that routing around a jam is an intervention, not an observation. Re-weight a blocked arterial and every router in the area produces the same answer in the same minute, so the quiet residential street that was the detour fills up, your own pings duly report it slowing, and you’ve manufactured the next jam — and possibly sent a wall of through-traffic past a school. The defense is to stop treating each request as independent: damp how fast weights are allowed to swing, spread drivers across several plausible alternatives instead of piling everyone onto the single best one, and cap how much load the network will push onto small residential roads whatever the graph says. The second is coverage. A live speed is only as good as the number of distinct devices that produced it, and a quiet road can be swung by one delivery van idling at the kerb. So I’d hold a minimum-sample floor per segment and fall back to that road’s historical profile for the time of day below it, rather than publishing a confident speed built from almost nothing.

What the new pieces do

ETA Serviceservice
Turns a route into a time estimate using live and historical speeds per segment, so the ETA reflects actual conditions, not just distance.
Traffic Datastore
Current speed per road segment, aggregated from millions of anonymized GPS pings. Feeds both ETAs and re-routing around jams.
Location Pingsbus
A firehose of anonymized location updates from phones in motion. Aggregated into live traffic speeds that keep ETAs and routes honest.

Back of the envelope

every moving phone = a sensor
100Ms of anonymized pings
aggregate ⇒ live speed / segment
real-time ground truth
re-weight edges ⇒ avoid jams
fastest, not just shortest

Step 6 · Names to places

Geocoding

Users type “Eiffel Tower” or “221B Baker St”, not latitude/longitude. The router and map need coordinates, and search results need human-readable addresses back.

Maps APItiles + routesGeocodingaddress ↔ latlng
New in this step: Geocoding.

Users type "Eiffel Tower" or "221B Baker St", but the router and tiles need coordinates. How do you bridge them?

  1. Forcing coordinates is hostile to users who think in names and addresses. The system must translate human input into coordinates, not push that burden onto people.

  2. Graph nodes are intersections, not a searchable place/address index — and addresses don’t map cleanly to nodes. Place lookup is its own retrieval problem with ranking and autocomplete.

  3. Geocoding (and reverse geocoding) translates names/addresses to lat-lng and back, backed by a places index with autocomplete and relevance/distance ranking — feeding clean coordinates into routing and the map.

A Geocoding service translates addresses and place names into coordinates (and reverse-geocodes coordinates into addresses), backed by a places index. It’s the bridge between what users type and what the graph and tiles understand.

Why this piece earns its place

Geocoding hands back a coordinate, and routing needs a position on the graph. Those aren’t the same thing, and the gap between them is where a surprising share of bad directions comes from. A place’s coordinate is usually its centroid, and a centroid sits in the middle of a building, a park or a car park — somewhere with no road under it at all. Snapping it to the nearest segment by raw distance is where it goes wrong: the closest line to a shop on a divided road is often the carriageway running the other way, or the trunk road rather than the service road beside it, and the router then computes a perfectly optimal route to a point the driver cannot actually stop at without carrying on to the next turnaround. So the places index should store an access point, not just a centroid — the entrance, snapped to a specific edge and a direction of travel, checked against one-ways — and routing should take that as the endpoint. Reverse geocoding has the mirror problem, which is why a GPS trace gets map-matched onto the graph rather than snapped to whatever is nearest.

What the new pieces do

Geocodingservice
Translates a typed address or place name into coordinates (and back), so “coffee near me” becomes points the map and router can use.

Step 7 · Scale the planet

The sharp edges

Billions of tile and route requests, a graph too big for one machine, and traffic that shifts by the minute. No single server holds the world or serves that load.

UserMaps APITile ServiceTile Store + CDNRouting EngineRoad GraphETA ServiceTraffic DataGeocodingLocation Pings
The system as it stands at this step. · swipe to pan the diagram

Lean on the CDN for tiles (most requests never hit your origin), partition the road graph by region with cross-region links for long routes, and continuously update edge weights from the traffic stream. Cache popular routes and precomputed hierarchies per region.

Why this piece earns its place

Partitioning by geography is right, and it concentrates the entire cost of the design into one place: the routes that cross a boundary. A naive cross-region query wants both regions in memory at once, which is exactly what partitioning was supposed to avoid, and long routes are the queries you least want to be slow. So the boundary has to become a first-class object rather than an accident of where you drew the lines. Each region publishes its border nodes; you precompute the costs between the borders of neighbouring regions — a small table, because a region has far fewer border nodes than interior ones — and a crossing query becomes local-to-border, a lookup across, then border-to-local. That makes the cut cheap, and usefully makes it a thing you can refresh on its own cadence. The other thing I’d watch is that geography is a shard key with wildly uneven load: an equal-area grid gives you one cell that is a dense city and its neighbour that is farmland. Cut regions by query volume instead, and accept small dense ones where the traffic actually is.

You did it

You just designed Google Maps.

UserMaps APITile ServiceTile Store + CDNRouting EngineRoad GraphETA ServiceTraffic DataGeocodingLocation Pings
The finished design, end to end. · swipe to pan the diagram

Everything you assembled, in order

  • Pre-rendered z/x/y map tiles served from a CDN make browsing instant.
  • Roads modeled as a weighted graph of intersections and segments.
  • A* routing (Dijkstra + heuristic) finds optimal paths efficiently.
  • Contraction hierarchies precompute shortcuts for millisecond long-distance routes.
  • Crowdsourced GPS pings aggregate into live traffic for honest ETAs and re-routing.
  • Geocoding bridges typed addresses/places to coordinates and back.
  • CDN tiles plus region-partitioned graphs scale to billions of requests.

Where an interviewer pokes next

Getting the boxes right is the easy half. These are the questions that separate a candidate who drew the diagram from one who has run the thing. Answer each one out loud before you open it.

  1. Why contraction hierarchies over just A*?

    A* prunes the search but still explores a swath proportional to route length, so a cross-continent query touches millions of nodes — too slow at scale. Contraction hierarchies do heavy offline preprocessing to add shortcut edges that let a query "hop" over entire regions, reducing a long route to a handful of edges answered in milliseconds. The classic trade: expensive build, cheap query, justified because the graph changes slowly and queries are constant.

  2. How does live traffic get folded in without rebuilding everything?

    Edge weights update continuously from the traffic stream, but contraction-hierarchy shortcuts are expensive to rebuild — so production uses customizable route planning (CRP) or live overlays: keep the static hierarchy, apply a fast "metric customization" layer that re-weights affected edges/shortcuts when traffic changes. Routing reads current weights; you re-customize periodically rather than re-contracting the whole graph.

  3. How are ETAs made accurate, not just current-speed × distance?

    Multiple signals: live per-segment speeds for the near term, historical speed-by-time-of-day for parts you’ll reach later, plus learned corrections (turn delays, light patterns, congestion build-up). The ETA predicts conditions along the trip’s timeline, not a single snapshot — which is why a 9am estimate differs from 9pm on the same route.

  4. How do you keep the map and graph fresh as roads change?

    Two pipelines: tiles are re-rendered offline and pushed to the CDN as new versions (old ones expire), and the road graph is updated from map data (new roads, closures, turn restrictions) then re-preprocessed for hierarchies region by region. Both are eventually-consistent batch jobs; urgent changes (a closure) apply as live edge-weight overrides ahead of the full rebuild.

  5. How would you scale to billions of tile + route requests?

    Geography is a natural shard key. Tiles are almost entirely served from the CDN edge (most requests never reach origin), and the road graph is partitioned by region with cross-region links for long routes, so a query mostly hits one region. Cache popular routes and per-region hierarchies. Most routes are local, so partition-by-space plus aggressive edge caching carries the planet-scale load.

Check yourself — the answers, and why

Eight steps in, these are the calls you should be able to make cold. Pick one, then read why.

  1. Map browsing is fast because the world is…

    Immutable, edge-cached tiles turn "render the world" into "fetch a few static images near the viewport".

  2. Routing runs over…

    Drawing and routing are separate: tiles for looking, a weighted graph for computing paths.

  3. A* beats plain Dijkstra by…

    An admissible straight-line heuristic prunes the search toward the destination while staying optimal.

  4. Continental routes return in milliseconds thanks to…

    Trade heavy offline preprocessing for cheap online queries that skip most of the graph.

  5. Live traffic data comes from…

    Every moving user is a sensor; aggregated motion becomes per-segment live speeds for honest ETAs.

Worked example: one drive to the Eiffel Tower, from typed name to re-route

A driver opens the map, types Eiffel Tower and taps Directions. Follow that one request through the build. The design gives no latencies or sizes, so every timing here is illustrative. What it does give is the structure: which store answers, which region, and which weights.

StepWhereWhat happensNumbers
1Tile Service + CDNThe app opens and requests only the tiles that cover its viewport, at versioned z/x/y paths. The edge answers, and the origin never sees them.15 tiles (412×915 px screen, 256 px tiles, illustrative)
2GeocodingThe places index ranks the text to one place and returns its access point, the entrance snapped to an edge and a direction, not the centroid in the middle of a lawn.1 place → 1 edge; ~20 ms, illustrative
3Maps API → regional shardThe origin and the access point fall in the same region, so the query loads one region's graph and hierarchy. A cross-region trip would add one border-table lookup.1 region, 0 border hops
4Road GraphEdges are weighted by travel time, with a turn table per node. Tonight's closure on a bridge is a huge weight, not a deleted edge, so the shortcuts built over it stay valid.cost = seconds, not metres
5Routing Engine (CH)An upward search from each end meets at the top of the hierarchy. The winning shortcuts are then unpacked back into real segments for turn-by-turn.milliseconds (config), no full-graph crawl
6ETA Service → Traffic DataWalks the route and sums segment times. Live speeds cover the next few kilometres, the historical profile covers segments reached later, and a segment under the sample floor uses history.near = live, far = time-of-day profile
7Location PingsWhile driving, the phone streams anonymized pings. Aggregated with other devices, they become one live speed per segment.1 driver = 1 sensor
8Traffic → RoutingA jam forms ahead. Its edge weights rise through the cheap customization layer, damped so every driver isn't sent down the same side street, and a re-route is offered.weights moved, 0 re-contractions

The trace splits cleanly. Row 1 is pure delivery and costs the origin almost nothing (Step 1). Rows 2–8 are search over a graph, and row 5 is cheap only because Step 4 paid for it offline. Row 6 is where accuracy is won or lost. At 10× load the CDN shrugs. The traffic loop in row 8 breaks first: more drivers sharing one detour make the next jam, which is why Step 5 damps and spreads re-routes. The hot city's shard is next, which is why Step 7 cuts regions by query volume, not area.

How you’d open this design in an interview

Before any boxes: agree what it must do, pin the qualities that shape everything, then build — naming each trade-off as you make it. The walkthrough above is that exact order.

What it must do

Agree on these before drawing a single box.

  • Draw the map: pan and zoom a smooth world map at every zoom level.
  • Route: compute the fastest path between two points over the road network.
  • ETA: a traffic-aware time estimate that reflects current conditions.
  • Search places: turn "Eiffel Tower" or an address into coordinates and back.
  • Re-route: steer around jams as live traffic changes.

The qualities that shape everything

Each one names the mechanism that buys it.

Instant pan/zoom worldwide
Pre-render the map into immutable z/x/y tiles served from a CDN, so the client fetches only the viewport and most requests never reach the origin.
Routing you can actually compute
Model roads as a weighted graph — intersections are nodes, segments are edges weighted by length and speed — separate from the visual tiles, turning directions into shortest-path search.
Optimal path without crawling the whole graph
A* — Dijkstra guided by a straight-line-distance heuristic — steers toward the destination, exploring a fraction of the nodes while staying optimal.
Millisecond continental routes
Contraction hierarchies precompute shortcut "highways" offline so a long query hops over most local roads — trading heavy build time for cheap queries.
ETAs that reflect real conditions
Aggregate anonymized GPS pings into live per-segment speeds; the ETA service weights the route by them and routing re-weights edges to avoid jams.
Bridge what users type to what the graph understands
A geocoding service translates addresses/place names to coordinates and back, via a places index with autocomplete and relevance ranking.

The trade-offs you say out loud

Senior signal isn’t the boxes — it’s naming what you gave up and why it was the right price.

Pre-rendered z/x/y tiles over rendering each viewport per request

Rendering on demand for billions of pans is enormously expensive and barely cacheable since every viewport differs. Immutable tiles turn "render the world" into "fetch a few static images," and they edge-cache beautifully.

A weighted road graph over routing on the display tiles

Tiles are pixels for looking at, not connectivity for computing over — you can’t route on images. A graph of intersections and weighted segments encodes distance, speed and legal turns for shortest-path search.

A* with a distance heuristic over plain Dijkstra outward search

Dijkstra expands in all directions over a continental graph. An admissible straight-line heuristic steers the search toward the goal, touching a fraction of the nodes while still returning the optimal route.

Contraction hierarchies (offline) over caching every origin-destination route

There are astronomically many O-D pairs — precomputing all routes is impossible to store. Precompute reusable shortcut edges instead, so long routes become a few hops at the top of the hierarchy in milliseconds.

Crowdsourced GPS pings over sensors on every road

Physical sensors on every road on earth are impossibly expensive and sparse. Hundreds of millions of moving phones are already sensors — aggregate their anonymized motion into live per-segment speeds.

The answer, out loud

What a strong answer to “Design Google Maps” sounds like, first question to last trade-off. It is about 7 minutes of talking; the whiteboard and the interviewer fill the rest of the 45. Read it aloud once, then close the page and give it yourself.

  1. 0–4 min

    Split the problem first

    Let me start by splitting this, because there are two systems here wearing one name. One is drawing the world — a map anywhere on earth, at any zoom, while someone drags it with their thumb. The other is computing over the world — given two points, the fastest way between them and when I’ll arrive. They share almost nothing. Drawing is a read-mostly delivery problem that should end up nearly free; routing is a search problem over an enormous graph, and that’s where the real engineering is. I’d keep them separate on purpose, so that when routing has a bad day people can still browse the map.

  2. 4–9 min

    Publish the map instead of serving it

    For the map itself I’d pre-render it into tiles, addressed by zoom level and an x and y in the grid at that level. The client asks only for the tiles its viewport covers, so the data moving is the size of a phone screen, not the size of a continent. And because a tile is just a file, this becomes a delivery problem: the overwhelming majority of tile requests get answered at an edge near the user and never reach us. I’m not serving the map, I’m publishing it, and someone else’s cache does the serving.

    Built in step 1: Map tiles
  3. 9–14 min

    Roads need a different representation

    Routing needs something completely different. You can’t compute a route on the tiles — they’re pictures, and a picture of a road doesn’t say what that road connects to. So I’d build a graph: intersections are nodes, segments are edges, every edge carries a weight. I’d weight by travel time rather than length, because people want the fastest route, and time is also the slot live traffic plugs into later. The edges carry the rules too — one-ways, restrictions, road class. It’s built offline from map data, and it’s what every routing feature reads.

    Built in step 2: The road graph
  4. 14–20 min

    Search with a hint, not in all directions

    Then the query. The textbook answer is Dijkstra, and it works, but it expands in every direction at once — ask for a route across the country and you’ve searched a huge circle of roads nobody would ever drive. A* is Dijkstra with a hint: order the frontier by cost so far plus an optimistic estimate of what’s left, and the search leans toward the destination. As long as that estimate never overestimates, the route you get back is still the best one — you just touched a fraction of the nodes to find it.

    Built in step 3: Shortest-path routing
  5. 20–27 min

    Precompute the boring middle

    A* is enough for a city and not enough for a continent at this query volume. So I’d precompute. The insight behind contraction hierarchies is that long routes are boring in the middle — everyone crossing the country funnels onto the same motorways — so heavy offline work adds shortcut edges standing for whole chains of road, and the query hops over the middle instead of walking it. You can’t cache the answers themselves; the number of start-and-end pairs is astronomical. You cache the structure that makes any of them cheap. Build time traded for query time, worth it because the network changes slowly and people ask constantly.

    Built in step 4: Precompute for speed
  6. 27–34 min

    The users are the sensors

    Now the part that makes it feel alive. The fastest route by posted speed limits isn’t the fastest route at six in the evening, so I need current speeds. I wouldn’t instrument the roads — you can’t put a sensor on every street on earth — I’d use the phones already moving along them. Anonymized pings arrive as a stream and aggregate into a live speed per segment, and two things read it: the ETA service, which walks the route and sums the time, and routing, which uses those speeds as edge weights so the search steers around the jam. And the ETA isn’t one snapshot — for the far end of a long trip you want that road’s historical profile for the time you’ll actually be there.

    Built in step 5: Live ETAs
  7. 34–39 min

    Nobody types coordinates

    There’s a piece at the front I haven’t covered: people type names and addresses, not latitude and longitude. So I need geocoding — a places index that turns text into a point, with autocomplete and ranking, because “coffee” has to resolve to the one near you. And the reverse too, coordinates back into an address, for a dropped pin or a shared location. It sits in front of both subsystems, and it’s a retrieval and ranking problem in its own right rather than a lookup table.

    Built in step 6: Geocoding
  8. 39–43 min

    Geography is the shard key

    At planet scale I’d shard by geography, which is convenient because most routes are local to one area. Tiles are already handled at the edge, so the origin only really sees re-renders. The graph gets partitioned by region, each region carries its own preprocessed hierarchy, and a typical query lands in one region and stays there. Popular routes and the hot regions’ structures stay warm in cache.

    Built in step 7: The sharp edges
  9. 43–45 min

    The trade-off, and what’s left

    So the trade-off I’d name: nearly everything here is precomputation buying query speed, and everything precomputed can be out of date — the tiles, the hierarchies, the speeds. The system is fast because it’s slightly stale everywhere, and most of the design work is choosing how stale each layer may be. With more time I’d go after re-routing properly, which is really the question of when a better route mid-trip is worth interrupting someone for, and the offline case — what a phone can still do with a downloaded region and no network.

What this teaches

Learn system design by building a mapping and navigation service like Google Maps step by step. An interactive guide covering map tiles and CDN delivery, the road graph, shortest-path routing with A* and contraction hierarchies, traffic-aware ETAs, geocoding, and scaling.

Key takeaways

  • Pre-rendered z/x/y map tiles served from a CDN make browsing instant.
  • Roads modeled as a weighted graph of intersections and segments.
  • A* routing (Dijkstra + heuristic) finds optimal paths efficiently.
  • Contraction hierarchies precompute shortcuts for millisecond long-distance routes.
  • Crowdsourced GPS pings aggregate into live traffic for honest ETAs and re-routing.
  • Geocoding bridges typed addresses/places to coordinates and back.
  • CDN tiles plus region-partitioned graphs scale to billions of requests.

Concepts covered

  • What is Google Maps?
  • Map tiles
  • The road graph
  • Shortest-path routing
  • Precompute for speed
  • Live ETAs
  • Geocoding
  • The sharp edges
built to be navigated, not memorized — make the calls, kill the traffic feed, run the gauntlet.
Finished this one? 0 / 65 System Designs done
NextDesign a Time-Series Database Closest system design to this one

Explore the topic

See this alongside everything else on the same subject — handbooks, system designs, challenges and tools, in one place.

More System Designs