FSD Frontend System Design

Part VI β€” The Blueprint Β· Lesson 11.1

HLD – Analytics Dashboard (Google Analytics)

In one lineAggregate on the server, render only what fits on screen, and never ship a million rows to a browser.

Where you've seen itChanging the date range on a dashboard and watching each card refresh on its own instead of the page going blank.

  • Time19 min
  • DiagramQuery pipeline + widget grid with independent loading
  • Chapter11 Β· High Level Design

Diagram

diagram
1–2 Β· SCOPE AND SCALE

  in scope   widget grid Β· filters Β· date range Β· drill-down Β· export
  out        data ingestion Β· ML insights Β· alerting config
  scale      billions of raw events Β· 10–20 widgets per dashboard
             queries take 200ms–10s depending on range
diagram
3 Β· THE PIPELINE β€” aggregation belongs on the server

  raw events (billions)
        β”‚  pre-aggregated by the warehouse
        β–Ό
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   GET /metrics?metric=sessions
  β”‚  Query service β”‚   &from=…&to=…&granularity=day&dims=country
  β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
          β”‚  returns ~365 points, not 40M rows
          β–Ό
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚  Dashboard                                 β”‚
  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
  β”‚  β”‚ KPI    β”‚β”‚ KPI    β”‚β”‚ time series      β”‚ β”‚  each widget owns
  β”‚  β”‚ 200ms  β”‚β”‚ 200ms  β”‚β”‚ 1.2s             β”‚ β”‚  its own query,
  β”‚  β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚  loading state,
  β”‚  β”‚ table  β”‚β”‚ map    β”‚β”‚ funnel  8s ⏳    β”‚ β”‚  and error state
  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

  One slow widget must never block the other eleven.
diagram
6a Β· DEEP DIVE β€” KEEPING THE UI RESPONSIVE

  problem                        fix
  ─────────────────────────────────────────────────────────────
  40M rows to plot               downsample server-side (LTTB) to
                                 ~1–2k points β€” visually identical
  huge table                     server pagination + virtualization
  filter change re-runs 12       debounce (10.15) + AbortController
    queries per keystroke        cancels superseded requests
  heavy client-side math         Web Worker, off the main thread (5.1)
  slow query blocks the page     per-widget suspense boundaries
  same query from 3 widgets      dedupe by cache key (6.9)
diagram
6b Β· DEEP DIVE β€” FILTER STATE AND SHAREABILITY

  URL is the source of truth (6.10)

  /dashboard?from=2026-01-01&to=2026-03-31&country=IN&metric=sessions
       β”‚
       β”œβ”€β–Ί every widget derives its query key from these params
       β”œβ”€β–Ί a shared link reproduces the EXACT view
       └─► back button steps through filter history

  Cache by the full key so switching back to a previous range
  is instant and costs no query.

How it works

  1. Server aggregates; the client receives chart-ready series, never raw events.
  2. Each widget is independent β€” own query, own cache key, own loading and error state.
  3. Downsample before plotting; beyond a couple of thousand points, more data adds no visible information.
  4. Filters live in the URL, making every dashboard state shareable and back-button friendly.
  5. Virtualize tables and move any heavy computation into a Web Worker.

Trade-offs

ChoiceCost
Server aggregationLess client flexibility; far less data transferred
Client aggregationInteractive slicing; only viable for small sets
Per-widget loadingMore requests; no blank-page stalls
Long cache TTLInstant re-renders; risk of stale numbers

Interview angle

"A widget needs to chart 40 million rows. How?"

  • It doesn't β€” the server aggregates to the granularity the chart can actually show, typically a few hundred points.
  • Downsample with an algorithm that preserves visual shape (LTTB) rather than naive sampling, which hides spikes.
  • Keep the widget independently loaded and cached by its query key so other cards render while it works.

Recap

  • Aggregate server-side; the browser gets chart-ready series.
  • Independent widgets: one slow query must not blank the dashboard.
  • Filters in the URL, cached by key β€” shareable and instant on return.