# Low Level Design: Map Tiles Service

Source: https://www.techinterview.org/post/3233471001/lld-map-tiles/
Updated: 2026-04-17 · techinterview.org

## Tile Coordinate System

Map tiles use a **Z/X/Y** (zoom / column / row) scheme:

  - Zoom 0 = 1 tile covering the entire world

  - Zoom 1 = 4 tiles (2x2 grid)

  - Zoom N = 2^N x 2^N tiles

  - Zoom 18 = 68 billion tiles — only generated on demand


```
tile_count(zoom) = 2^zoom * 2^zoom
tile_url = /tiles/{z}/{x}/{y}.png
```


## Tile Formats

  - **Raster (PNG/JPEG)** — pre-rendered images; simple to serve, large storage footprint

  - **Vector (MVT/Protobuf)** — raw geometry data; dynamic client-side styling, smaller payloads

## Tile Generation Pipeline


```
OpenStreetMap data (PBF)
  -> osm2pgsql -> PostGIS database
  -> Tile Renderer
       Raster: Mapnik  -> PNG tiles
       Vector: Tegola  -> MVT tiles
  -> Object Storage (S3): tiles/{z}/{x}/{y}.png
  -> CDN (CloudFront / Fastly)
  -> Client
```


## Storage Layout


```
s3://map-tiles-bucket/
  raster/
    0/0/0.png
    1/0/0.png
    ...
    18/x/y.png
  vector/
    0/0/0.mvt
    ...

-- Optional metadata table
CREATE TABLE tile_metadata (
  zoom         SMALLINT,
  x            INT,
  y            INT,
  generated_at TIMESTAMP,
  size_bytes   INT,
  PRIMARY KEY (zoom, x, y)
);
```


## CDN Delivery

  - Tiles are served from CDN edge nodes; S3 is the origin

  - Tiles are **immutable** for a given zoom+coordinate once rendered

  - Set `Cache-Control: public, max-age=31536000, immutable` for indefinite CDN caching

  - Low origin hit rate after warmup — CDN absorbs >95% of traffic

## Metatile Rendering

Render an **8x8 block** (metatile = 64 tiles) in a single pass to amortize renderer startup cost and eliminate seam artifacts at tile borders. Split result into 64 individual tile files before uploading to S3.


```
metatile_render(z, meta_x, meta_y):
  image = render_8x8_block(z, meta_x*8, meta_y*8)
  for dx in 0..7:
    for dy in 0..7:
      tile = crop(image, dx*256, dy*256, 256, 256)
      s3_put('tiles/{z}/{meta_x*8+dx}/{meta_y*8+dy}.png', tile)
```


## Pre-generation vs On-demand

  - **Zoom 0-14**: pre-generate all tiles offline during initial build and after map data updates

  - **Zoom 15-18**: render on-demand per request, cache result in S3/CDN immediately

## Tile Invalidation

When map data is updated, re-render only tiles intersecting the changed geographic bounding box:


```
changed_bbox = (min_lng, min_lat, max_lng, max_lat)

for zoom in 0..14:
  x_min, y_min = lng_lat_to_tile(changed_bbox.min_lng, changed_bbox.max_lat, zoom)
  x_max, y_max = lng_lat_to_tile(changed_bbox.max_lng, changed_bbox.min_lat, zoom)
  enqueue_render_jobs(zoom, x_min..x_max, y_min..y_max)
```


## Rate Limiting

Apply per-API-key rate limiting at the CDN or API gateway layer to prevent bulk tile scraping. Unauthenticated requests get a lower tier limit.


```
-- Nginx rate limit example
limit_req_zone $binary_remote_addr zone=tiles:10m rate=500r/m;
limit_req zone=tiles burst=100 nodelay;
```


See also: [Netflix Interview Guide 2026: Streaming Architecture, Recommendation Systems, and Engineering Excellence](https://www.techinterview.org/post/3233460274/netflix-interview-guide-2026-streaming-architecture-recommendation-systems-and-engineering-excellence/)

See also: [Uber Interview Guide 2026: Dispatch Systems, Geospatial Algorithms, and Marketplace Engineering](https://www.techinterview.org/post/3233460273/uber-interview-guide-2026-dispatch-systems-geospatial-algorithms-and-marketplace-engineering/)

See also: [Airbnb Interview Guide 2026: Search Systems, Trust and Safety, and Full-Stack Engineering](https://www.techinterview.org/post/3233460279/airbnb-interview-guide-2026-search-systems-trust-and-safety-and-full-stack-engineering/)
