Infinite Maps Marker UI
Frontend system design for map UIs — viewport queries, marker clustering, tiles, performance, and selection UX.
- system-design
- interview
- architecture
- maps
Scope the problem
Design a map-heavy UI (store locator, listings on map, delivery tracking): pan/zoom loads relevant markers without melting the browser.
In scope: viewport-driven data fetch, clustering, marker rendering performance, detail selection, mobile gestures.
Out of scope: building a tile server; use Mapbox/Google/MapLibre as the engine.
Requirements
| Type | Examples |
|---|---|
| Functional | pan/zoom, markers, click → detail, search this area |
| Non-functional | 60fps pan; don’t fetch whole world |
| Scale | 100k catalog → tens of markers on screen |
Architecture
MapEngine (MapLibre/GL)
├── Tile layer (raster/vector)
├── Marker layer / GeoJSON source
├── Cluster layer
└── UI overlay (React)
├── Search / filters
├── Result list (sync with map)
└── Selected card
Keep map camera state as source of truth for “what data to load”; list and map stay coordinated.
Data loading: viewport queries
type BBox = {
west: number;
south: number;
east: number;
north: number;
};
// GET /pois?bbox=w,s,e,n&zoom=12&filters...
On moveend (debounced 200–300ms):
- Read bounds + zoom
- Abort prior request
- Fetch POIs for bbox
- Diff into map source
At low zoom, don’t return raw 50k points — server returns clusters or aggregated counts.
Tile/vector alternatives
- Server vector tiles with points already baked — great at scale
- Client clustering libraries for medium sets
Clustering
zoom < 12 → cluster bubbles with counts
zoom ≥ 14 → individual markers
Click cluster → easeTo zoom expand. Accessibility: list panel still browsable without map precision.
Rendering performance
| Technique | Why |
|---|---|
| Map GL layers not DOM markers | GPU batching |
| DOM markers only for selected/hover | expensive |
| Limit labels | collision |
| Simplify geometries | tile size |
| Worker for heavy GeoJSON prep | main thread |
Avoid React re-rendering thousands of marker components — use map source setData.
State model
type MapUiState = {
center: [number, number];
zoom: number;
bounds: BBox;
selectedId: string | null;
filters: Filters;
items: Poi[]; // last fetch
};
URL sync: ?lat=&lng=&z=&id= for shareable views.
List ↔ map sync
- Hover list row → highlight marker
- Click marker → scroll list into view
- Filters apply to both; empty states clear layers
Mobile UX
- Full-screen map + bottom sheet results
- Large hit targets
- Respect reduced data: lower tile quality
- Geolocation permission priming
Caching
| Layer | What |
|---|---|
| HTTP | tile caching by CDN |
| Memory | recent bbox results keyed by quantized bounds |
| Session | last camera |
Quantize bbox keys to improve cache hits (toFixed(3)).
Offline
Limited: cache recent tiles + last POIs; show stale banner. Full offline maps are product-specific downloads.
Accessibility
- Don’t map-only: provide list and search
- Keyboard: list navigation primary; map is progressive enhancement
- Announce selected place name
Tradeoffs
- DOM markers vs GL layers
- Server clusters vs client
- Fetch on move vs “Search this area” button (cost control)
- Third-party map cost/quotas vs self-host tiles
Interview close
Viewport bbox queries with abort → zoom-dependent clustering → GL layers for volume → React for chrome only → URL camera state → list fallback for a11y. Call out not downloading the world.
Filter + map composition
Filters (open now, category, price) always hit the server with bbox. Client-only filtering of a previous world download does not scale. Debounce filter chips with the same moveend coalescing so you don’t N× network.
Marker states
default · hover · selected · clustered
Only selected uses a heavier DOM card anchored to projection coordinates updated on move.
Testing strategy
- Unit: bbox serialization, cluster expand zoom math
- Integration: mock map events → assert fetch called with bounds
- Visual: reduced-motion; high-contrast pins
Cost control
“Search this area” button instead of fetch-on-every-pan reduces API bills for browsing users. Auto-search is nicer UX — gate it behind zoom ≥ threshold.
Related on this site
- Virtualized Data Table
- Search Results Page Design
- Frontend Caching Strategies
- System Design Interview Framework