Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save carefree-ladka/fba8c48594736555339428d454d035b6 to your computer and use it in GitHub Desktop.

Select an option

Save carefree-ladka/fba8c48594736555339428d454d035b6 to your computer and use it in GitHub Desktop.
Frontend System Design Interview Guide (RADIO Framework)

Frontend System Design Interview Guide (RADIO Framework)

RADIO = Requirements → Architecture → Data Model → Interface (API) → Optimizations


Table of Contents

  1. Google Sheets

  2. PayPal

  3. Google Maps

  4. WhatsApp


Time Budget (60-min interview)

Step % Time Minutes
Requirements <15% ~8 min
Architecture ~20% ~12 min
Data Model ~10% ~6 min
Interface/API ~15% ~9 min
Optimizations ~40% ~25 min


1. Google Sheets

1.1 Requirements

Clarifying Questions to Ask

  • Should we support multi-user real-time collaboration (like Google Docs)?
  • Which cell data types: text, numbers, formulas, dates, booleans?
  • Do we need formula evaluation (SUM, VLOOKUP, etc.)?
  • Should we support formatting (bold, colors, borders)?
  • Do we need charts, conditional formatting, data validation?
  • What is the max sheet size? (e.g., 10M cells)
  • Import/export (CSV, XLSX)?

Functional Requirements

  • Render a large 2D grid of rows and columns
  • Cell selection (single, range, multi-range)
  • Inline cell editing
  • Formula support: =SUM(A1:A10), =IF(), cell references
  • Copy/paste, undo/redo
  • Column/row resize, freeze rows/columns
  • Basic formatting: bold, italic, font size, background color
  • Auto-save

Non-Functional Requirements

  • Handle spreadsheets with up to 1 million rows smoothly
  • Formula recalculation < 100ms for simple sheets
  • Collaborative edits with < 300ms conflict resolution
  • Offline support with sync on reconnect

1.2 Architecture

┌──────────────────────────────────────────────────────────────┐
│                        Browser Client                        │
│                                                              │
│  ┌─────────────────────────────────────────────────────────┐ │
│  │                    Sheet UI Layer                       │ │
│  │  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  │ │
│  │  │  Canvas/Grid │  │  Formula Bar │  │  Toolbar     │  │ │
│  │  │  Renderer    │  │  Component   │  │  (Format UI) │  │ │
│  │  └──────┬───────┘  └──────┬───────┘  └──────┬───────┘  │ │
│  └─────────┼─────────────────┼─────────────────┼──────────┘ │
│            │                 │                 │             │
│  ┌─────────▼─────────────────▼─────────────────▼──────────┐ │
│  │                  Sheet Controller                       │ │
│  │  (Handles input events, selection, clipboard, undo/redo)│ │
│  └────────────────────────┬───────────────────────────────┘ │
│                           │                                  │
│  ┌────────────────────────▼───────────────────────────────┐ │
│  │                  Sheet Store (Client)                   │ │
│  │  ┌───────────────┐  ┌────────────────┐                 │ │
│  │  │  Cell Data    │  │  Formula Engine│                 │ │
│  │  │  (sparse map) │  │  (dependency   │                 │ │
│  │  │               │  │   graph + eval)│                 │ │
│  │  └───────────────┘  └────────────────┘                 │ │
│  │  ┌───────────────┐  ┌────────────────┐                 │ │
│  │  │  Undo/Redo    │  │  Selection     │                 │ │
│  │  │  Stack        │  │  State         │                 │ │
│  │  └───────────────┘  └────────────────┘                 │ │
│  └────────────────────────────────────────────────────────┘ │
│                                                              │
│  ┌────────────────────────────────────────────────────────┐ │
│  │              Collaboration Layer (OT/CRDT)              │ │
│  │         WebSocket client + operation queue              │ │
│  └───────────────────────┬────────────────────────────────┘ │
└──────────────────────────┼───────────────────────────────────┘
                           │ WebSocket / HTTP
              ┌────────────▼────────────┐
              │         Server          │
              │  (REST + WS endpoints)  │
              └─────────────────────────┘

Component Responsibilities

Component Responsibility
Canvas/Grid Renderer Renders visible cells only (virtualization). Uses <canvas> or virtual DOM rows.
Formula Bar Shows raw formula/value of active cell. Supports formula editing with autocomplete.
Toolbar Formatting controls: bold, font, color, alignment.
Sheet Controller Processes keyboard/mouse events, manages selection, delegates to store.
Sheet Store Source of truth for all cell data, formats, metadata. Sparse Map: {row}:{col} → Cell.
Formula Engine Parses formulas into ASTs, builds dependency graph (DAG), evaluates cells in topological order.
Collaboration Layer Transforms concurrent ops (OT) or merges CRDTs. Maintains op log for sync.

1.3 Data Model

Server-Originated Entities

// Core cell object (stored sparse - only non-empty cells)
interface Cell {
  row: number;
  col: number;
  rawValue: string;          // What user typed: "=SUM(A1:A3)" or "hello" or "42"
  computedValue: string | number | boolean | null;  // Evaluated result
  format?: CellFormat;
  dataType: 'string' | 'number' | 'boolean' | 'formula' | 'error';
}

interface CellFormat {
  bold?: boolean;
  italic?: boolean;
  underline?: boolean;
  fontSize?: number;
  fontColor?: string;        // hex
  backgroundColor?: string;  // hex
  textAlign?: 'left' | 'center' | 'right';
  numberFormat?: string;     // e.g. "0.00", "$#,##0"
  borders?: BorderConfig;
}

interface Sheet {
  id: string;
  name: string;
  cells: Map<string, Cell>;  // key = "row:col", sparse
  rowMetadata: Map<number, RowMeta>;   // height, hidden
  colMetadata: Map<number, ColMeta>;   // width, hidden
  frozenRows: number;
  frozenCols: number;
}

interface Spreadsheet {
  id: string;
  title: string;
  sheets: Sheet[];
  collaborators: Collaborator[];
  lastModified: number;
}

Client-Only State

interface SelectionState {
  activeCell: { row: number; col: number };
  selectionRange: Range | null;     // { startRow, startCol, endRow, endCol }
  multiSelections: Range[];
}

interface UndoRedoStack {
  undoStack: Operation[];
  redoStack: Operation[];
}

interface ViewportState {
  scrollTop: number;
  scrollLeft: number;
  visibleRowStart: number;
  visibleRowEnd: number;
  visibleColStart: number;
  visibleColEnd: number;
}

interface EditingState {
  isEditing: boolean;
  editCell: { row: number; col: number } | null;
  draftValue: string;
}

Formula Engine Internals

interface FormulaNode {
  type: 'literal' | 'cell_ref' | 'range_ref' | 'function' | 'operator';
  value?: any;
  args?: FormulaNode[];
}

interface DependencyGraph {
  // Cell A depends on cells B, C — if B changes, re-eval A
  dependents: Map<string, Set<string>>;   // "B1" → {"A1", "C3"}
  dependencies: Map<string, Set<string>>; // "A1" → {"B1", "C1"}
}

1.4 Interface / API

Server REST APIs

GET /api/spreadsheets/:id
→ { spreadsheet: Spreadsheet }

GET /api/spreadsheets/:id/sheets/:sheetId/cells?rowStart=0&rowEnd=100&colStart=0&colEnd=26
→ { cells: Cell[] }   // paginated/windowed fetch

PATCH /api/spreadsheets/:id/sheets/:sheetId/cells
Body: { operations: CellOperation[] }
→ { version: number, applied: Operation[] }

POST /api/spreadsheets/:id/sheets
Body: { name: string }
→ { sheet: Sheet }

WebSocket (Collaboration) API

// Client → Server
{ type: 'op', sheetId: string, op: Operation, baseVersion: number }

// Server → Client (broadcast to collaborators)
{ type: 'op', op: Operation, version: number, authorId: string }
{ type: 'presence', userId: string, cell: {row: number, col: number} }

// Operation types
type Operation =
  | { type: 'SET_CELL'; row: number; col: number; value: string; format?: CellFormat }
  | { type: 'DELETE_CELLS'; range: Range }
  | { type: 'INSERT_ROW'; afterRow: number; count: number }
  | { type: 'DELETE_ROW'; row: number; count: number }
  | { type: 'MOVE_CELLS'; from: Range; to: { row: number; col: number } }

Client-Client API (Controller → Store)

// Sheet Store public interface
interface SheetStore {
  getCell(row: number, col: number): Cell | undefined;
  setCellValue(row: number, col: number, value: string): void;
  getCellsInRange(range: Range): Cell[];
  applyFormat(range: Range, format: Partial<CellFormat>): void;
  undo(): void;
  redo(): void;
  pasteFromClipboard(targetCell: { row: number; col: number }): void;
}

1.5 Optimizations & Deep Dive

1. Grid Virtualization (Critical)

Rendering millions of cells with real DOM nodes is impossible. Use windowed rendering — only render visible rows/cols + overscan buffer.

Total rows: 1,000,000
Visible rows at once: ~30
Row height: 25px

Rendered DOM nodes: ~40 rows × ~20 cols = 800 nodes
(vs 1M × 20 = 20M without virtualization)

Canvas rendering (used by Google Sheets) is even faster:

  • Draw cell backgrounds, borders, text directly to <canvas>
  • Single repaint for entire viewport
  • Overlay <input> only on active cell for editing

Scroll handling:

// Translate canvas origin based on scroll position
// Only re-render if visible rows/cols change
onScroll(scrollTop, scrollLeft) {
  const newRowStart = Math.floor(scrollTop / ROW_HEIGHT);
  if (newRowStart !== this.visibleRowStart) {
    this.visibleRowStart = newRowStart;
    this.render(); // Only re-render when viewport changes
  }
}

2. Formula Engine Design

Input: "=SUM(A1:B3) + C1*2"

1. Tokenize → [FUNC:SUM, LPAREN, RANGE:A1:B3, RPAREN, PLUS, REF:C1, MULT, NUM:2]
2. Parse → AST
3. Build dependency edges: this cell depends on A1,A2,A3,B1,B2,B3,C1
4. Evaluate: DFS on AST, resolve cell refs from store
5. Store computed value
6. On change to A1: look up dependents, re-evaluate in topological order

Circular dependency detection: DFS with visited set — throw #CIRC! error.

Web Worker offloading: Run formula evaluation in a Web Worker to avoid blocking UI thread during heavy recalculation.

3. Collaborative Editing with OT

Operational Transformation ensures consistency when two users edit simultaneously:

User A (local):  SET_CELL(B2, "100")   version=5
User B (remote): INSERT_ROW(after=1)   version=5

After OT transform:
  A's op on B's state: SET_CELL(B3, "100")  ← row shifted down
  B's op on A's state: INSERT_ROW(after=1)  ← unchanged

Or use CRDTs (Conflict-free Replicated Data Types) for simpler conflict resolution at the cost of more memory.

4. Performance Optimizations

Technique Detail
Sparse data structure Use Map<string, Cell> not 2D array — only store non-empty cells
Dirty cell tracking Only recompute cells whose inputs changed
Batch updates Debounce PATCH requests (300ms), send delta ops not full sheet
Incremental rendering Re-render only changed cells via requestAnimationFrame
Web Workers Formula recalc, CSV import/export off main thread
IndexedDB Cache sheet data locally for offline and fast cold start

5. Undo/Redo (Command Pattern)

interface Operation {
  do(): void;
  undo(): void;
}

class SetCellOperation implements Operation {
  constructor(
    private store: SheetStore,
    private row: number, private col: number,
    private newValue: string,
    private oldValue: string
  ) {}
  do() { this.store.setCellRaw(this.row, this.col, this.newValue); }
  undo() { this.store.setCellRaw(this.row, this.col, this.oldValue); }
}

Collaborative undo is hard — in Google Sheets, undo is local-first (undoes your own operations only, not other users').

6. Security

  • Formula injection: sanitize/escape cell values rendered as HTML
  • CSV injection: when exporting, prefix formulas with apostrophe
  • Server-side formula evaluation for untrusted inputs
  • CSRF protection on all mutation endpoints


2. PayPal

2.1 Requirements

Clarifying Questions to Ask

  • What core flows: send money, receive money, transaction history, wallet/balance?
  • Do we need to handle multiple currencies and exchange rates?
  • What payment methods: bank accounts, credit cards, PayPal balance?
  • Should we design the checkout/payment button embed (SDK)?
  • KYC/identity verification needed?
  • Mobile-first or responsive web?
  • Two-factor authentication flows?

Functional Requirements

  • User dashboard: balance, recent transactions
  • Send money to another user (email/phone)
  • Request money from another user
  • Transaction history with filters (date, amount, type)
  • Add/manage payment methods (bank, card)
  • PayPal.me personal payment link
  • Notifications for payment received/sent

Non-Functional Requirements

  • Security-first: PCI-DSS compliance, end-to-end encryption
  • Transaction confirmation < 2s
  • 99.99% uptime (financial system)
  • Idempotent payment APIs (no double-charges)
  • Full audit trail for all financial operations

2.2 Architecture

┌──────────────────────────────────────────────────────────────────┐
│                          Browser Client                          │
│                                                                  │
│  ┌────────────────────────────────────────────────────────────┐  │
│  │                        View Layer                          │  │
│  │  ┌─────────────┐  ┌─────────────┐  ┌──────────────────┐  │  │
│  │  │  Dashboard  │  │  Send Money │  │  Transaction     │  │  │
│  │  │  (Balance + │  │  Flow       │  │  History         │  │  │
│  │  │  Recent TX) │  │  (Stepper)  │  │  (Paginated)     │  │  │
│  │  └─────────────┘  └─────────────┘  └──────────────────┘  │  │
│  │  ┌─────────────┐  ┌─────────────┐  ┌──────────────────┐  │  │
│  │  │  Wallet /   │  │  Request    │  │  Notifications   │  │  │
│  │  │  Payment    │  │  Money      │  │  Panel           │  │  │
│  │  │  Methods    │  │  Flow       │  │                  │  │  │
│  │  └─────────────┘  └─────────────┘  └──────────────────┘  │  │
│  └────────────────────────────────────────────────────────────┘  │
│                                │                                  │
│  ┌─────────────────────────────▼──────────────────────────────┐  │
│  │                    Payment Controller                       │  │
│  │         (Multi-step form orchestration, validation)         │  │
│  └─────────────────────────────┬──────────────────────────────┘  │
│                                │                                  │
│  ┌─────────────────────────────▼──────────────────────────────┐  │
│  │                      Client Store                           │  │
│  │  ┌──────────────┐  ┌──────────────┐  ┌─────────────────┐  │  │
│  │  │  User/Auth   │  │  Transactions│  │  Payment        │  │  │
│  │  │  State       │  │  Cache       │  │  Methods Cache  │  │  │
│  │  └──────────────┘  └──────────────┘  └─────────────────┘  │  │
│  └────────────────────────────────────────────────────────────┘  │
│                                │                                  │
│  ┌─────────────────────────────▼──────────────────────────────┐  │
│  │  Security Layer: HTTPS-only, CSP, token rotation, 2FA      │  │
│  └────────────────────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────────────────────┘
                                 │ HTTPS (TLS 1.3)
              ┌──────────────────▼──────────────────────┐
              │              API Gateway                │
              │  Auth ─── Payment Service ─── Notif.   │
              └─────────────────────────────────────────┘

Key Design Decisions

  • No client-side balance modification: All balance reads are fresh from server; never trust client-computed balance.
  • Idempotency keys: Every payment request includes a client-generated idempotency key to prevent duplicate charges on retry.
  • Multi-step send flow: Amount → Recipient → Review → Confirm → Success — never a single form submit.

2.3 Data Model

interface User {
  id: string;
  email: string;
  fullName: string;
  profilePhotoUrl?: string;
  balance: {
    amount: number;
    currency: string;
  };
  isVerified: boolean;
}

interface Transaction {
  id: string;
  type: 'SEND' | 'RECEIVE' | 'REQUEST' | 'REFUND' | 'WITHDRAWAL' | 'DEPOSIT';
  status: 'PENDING' | 'COMPLETED' | 'FAILED' | 'CANCELLED';
  amount: number;
  currency: string;
  fromUser: UserSummary;
  toUser: UserSummary;
  note?: string;
  createdAt: number;
  completedAt?: number;
  paymentMethodId?: string;
  idempotencyKey: string;
}

interface PaymentMethod {
  id: string;
  type: 'BANK_ACCOUNT' | 'CREDIT_CARD' | 'DEBIT_CARD' | 'PAYPAL_BALANCE';
  displayName: string;     // "Visa ending in 4242"
  isDefault: boolean;
  isVerified: boolean;
  // Never store raw card numbers client-side
}

interface UserSummary {
  id: string;
  name: string;
  email: string;
  profilePhotoUrl?: string;
}

Client-Only State

interface SendMoneyDraft {
  step: 'amount' | 'recipient' | 'review' | 'confirm';
  amount: number | null;
  currency: string;
  recipientQuery: string;
  resolvedRecipient: UserSummary | null;
  selectedPaymentMethodId: string | null;
  note: string;
  idempotencyKey: string;  // generated on flow start, persisted across retries
}

interface TransactionFilters {
  dateRange: { from: Date; to: Date } | null;
  type: Transaction['type'] | 'ALL';
  amountMin: number | null;
  amountMax: number | null;
}

2.4 Interface / API

User & Balance

GET /api/v1/me
→ { user: User }

GET /api/v1/me/balance
→ { balance: { amount: number, currency: string }, asOf: timestamp }

Transactions

GET /api/v1/transactions
  ?cursor=xxx&size=20&type=SEND&dateFrom=2024-01-01&dateTo=2024-12-31
→ {
    transactions: Transaction[],
    pagination: { nextCursor: string | null, hasMore: boolean }
  }

GET /api/v1/transactions/:id
→ { transaction: Transaction }

Send Money (Critical Flow)

# Step 1: Look up recipient
GET /api/v1/users/search?q=john@example.com
→ { users: UserSummary[] }

# Step 2: Initiate payment
POST /api/v1/payments
Headers: { 'Idempotency-Key': 'client-uuid-v4' }
Body: {
  toUserId: string,
  amount: number,
  currency: string,
  paymentMethodId: string,
  note?: string
}
→ {
    transaction: Transaction,
    requiresVerification: boolean,  // triggers 2FA if true
    verificationToken?: string
  }

# Step 3: Confirm with 2FA (if required)
POST /api/v1/payments/:transactionId/verify
Body: { code: string, verificationToken: string }
→ { transaction: Transaction }  // status: COMPLETED

Payment Methods

GET /api/v1/payment-methods
→ { methods: PaymentMethod[] }

POST /api/v1/payment-methods
Body: { type: string, token: string }  // token from payment tokenization service
→ { method: PaymentMethod }

DELETE /api/v1/payment-methods/:id
→ { success: true }

2.5 Optimizations & Deep Dive

1. Security (Top Priority for Fintech)

┌─────────────────────────────────────────────────┐
│              Security Layers                     │
│                                                  │
│  Transport: TLS 1.3, HSTS, Certificate Pinning  │
│  Auth: JWT (short-lived 15min) + refresh tokens  │
│  2FA: TOTP (authenticator app) or SMS OTP       │
│  CSRF: SameSite=Strict cookies + CSRF tokens    │
│  CSP: Strict Content-Security-Policy headers    │
│  Sensitive Data: Never in localStorage          │
│                  Use httpOnly cookies only       │
└─────────────────────────────────────────────────┘

PCI DSS compliance: Never touch raw card numbers. Use a PCI-compliant JS library (Braintree.js, Stripe.js) that tokenizes cards in an iframe from the payment provider's domain. Only the token reaches your server.

2. Idempotency Pattern

// Generate UUID once at start of send flow
const idempotencyKey = crypto.randomUUID();

// On retry (network failure), send the SAME key
async function sendPayment(payload) {
  return fetch('/api/v1/payments', {
    method: 'POST',
    headers: { 'Idempotency-Key': idempotencyKey },
    body: JSON.stringify(payload)
  });
  // Server returns same transaction if key already processed — no double charge
}

3. Optimistic UI vs. Pessimistic UI

For financial operations, pessimistic UI is correct:

  • Do NOT show success before server confirms
  • Show loading spinner during payment processing
  • Only update transaction list after server 200 response
  • Show full error details on failure with retry option

Exception: You can optimistically add a "Pending" transaction to the list while waiting for server, but clearly label it as pending.

4. Transaction History Performance

Infinite scroll with cursor-based pagination:
- Cursor: opaque token encoding last TX id + timestamp
- 20 items per page
- Cache fetched pages in client store
- Stale-while-revalidate: show cached data immediately, refresh in background
- Virtual list for long histories (react-window)

5. Multi-Step Form State Machine

States: idle → amount → recipient → review → confirming → success | error

Transitions:
  amount + NEXT → recipient
  recipient + NEXT → review
  review + SUBMIT → confirming
  confirming + API_SUCCESS → success
  confirming + API_ERROR → error (retry available, same idempotency key)
  any + CANCEL → idle (clear draft)

Key: Lock "Back" button during confirming state
     Show session timeout warning if user idle > 10 min

6. Accessibility

  • All form fields with proper <label> associations
  • Error messages linked to inputs via aria-describedby
  • Currency amounts announced correctly by screen reader: "One hundred twenty-three dollars and 45 cents"
  • Focus management through multi-step flow: auto-focus first field of each step
  • Sufficient color contrast for all transaction status badges


3. Google Maps

3.1 Requirements

Clarifying Questions to Ask

  • Core features: map display, search, directions, or all three?
  • What travel modes: driving, walking, transit, cycling?
  • Do we need real-time traffic/live ETAs?
  • Should we support offline maps?
  • Street View? Satellite/terrain views?
  • Location sharing between users?
  • Custom map markers / business listings / reviews?
  • Mobile web vs. desktop?

Functional Requirements

  • Interactive map: pan, zoom (pinch/scroll), map style toggle (road/satellite)
  • Search for places (autocomplete + results)
  • Place detail panel (name, address, hours, photos, reviews)
  • Get directions: A → B, multi-stop routes
  • Turn-by-turn navigation mode
  • Current location (GPS)
  • Save places (favorites)

Non-Functional Requirements

  • Map tiles load within 500ms on 4G
  • Autocomplete results < 100ms
  • Smooth 60fps pan/zoom
  • Offline capability for saved maps
  • Map loads progressively (tiles appear as they arrive)

3.2 Architecture

┌──────────────────────────────────────────────────────────────────┐
│                          Browser Client                          │
│                                                                  │
│  ┌────────────────────────────────────────────────────────────┐  │
│  │                      View Layer                            │  │
│  │  ┌──────────────────────────────────────────────────────┐  │  │
│  │  │                  Map Canvas                          │  │  │
│  │  │  (WebGL/Canvas tile renderer + marker overlay layer) │  │  │
│  │  └──────────────────────────────────────────────────────┘  │  │
│  │  ┌──────────────┐  ┌──────────────┐  ┌─────────────────┐  │  │
│  │  │  Search Box  │  │  Place Detail│  │  Directions     │  │  │
│  │  │  (Autocomplete│  │  Panel       │  │  Panel          │  │  │
│  │  │   overlay)   │  │  (sidebar)   │  │  (route steps)  │  │  │
│  │  └──────────────┘  └──────────────┘  └─────────────────┘  │  │
│  └────────────────────────────────────────────────────────────┘  │
│                                │                                  │
│  ┌─────────────────────────────▼──────────────────────────────┐  │
│  │                     Map Controller                          │  │
│  │  (Pan/zoom events, URL state sync, mode switching)         │  │
│  └──────────────┬────────────────────────┬────────────────────┘  │
│                 │                        │                        │
│  ┌──────────────▼──────────┐  ┌─────────▼──────────────────┐    │
│  │  Tile Manager           │  │  Map Store (Client)         │    │
│  │  (fetch, cache, render) │  │  searchResults, route,      │    │
│  │  LRU cache for tiles    │  │  selectedPlace, viewport,   │    │
│  │  IndexedDB for offline  │  │  savedPlaces, userLocation  │    │
│  └──────────────┬──────────┘  └────────────────────────────┘    │
└─────────────────┼────────────────────────────────────────────────┘
                  │ CDN (tiles) / HTTPS (API)
       ┌──────────▼──────────────────────────────────┐
       │  Map Tile CDN    │  Places API  │ Routes API │
       └─────────────────────────────────────────────┘

Rendering Engine

Google Maps uses WebGL (via Mapbox GL or custom) for:

  • Hardware-accelerated tile rendering
  • Smooth 60fps tilting and rotation
  • 3D buildings layer
  • Vector tiles (scalable, styleable) vs. raster tiles (pre-rendered PNG)

3.3 Data Model

interface Viewport {
  center: LatLng;
  zoom: number;           // 0-22
  bearing: number;        // rotation 0-360
  tilt: number;           // 3D tilt 0-60
  bounds: LatLngBounds;   // computed from center+zoom+screen size
}

interface LatLng {
  lat: number;
  lng: number;
}

interface LatLngBounds {
  sw: LatLng;
  ne: LatLng;
}

interface Place {
  placeId: string;
  name: string;
  address: string;
  location: LatLng;
  types: string[];         // ['restaurant', 'food', 'establishment']
  rating?: number;
  reviewCount?: number;
  photos?: PlacePhoto[];
  openingHours?: OpeningHours;
  phone?: string;
  website?: string;
  priceLevel?: 1 | 2 | 3 | 4;
}

interface Route {
  distanceMeters: number;
  durationSeconds: number;
  polyline: LatLng[];      // encoded path to draw on map
  steps: RouteStep[];
  legs: RouteLeg[];
  travelMode: 'DRIVE' | 'WALK' | 'TRANSIT' | 'BICYCLE';
  trafficCondition?: 'NORMAL' | 'SLOW' | 'TRAFFIC_JAM';
}

interface RouteStep {
  instruction: string;     // "Turn right onto Market St"
  distanceMeters: number;
  durationSeconds: number;
  startLocation: LatLng;
  endLocation: LatLng;
  maneuver?: string;       // 'turn-right', 'merge', 'uturn'
  polyline: LatLng[];
}

interface MapTile {
  x: number;   // tile grid x
  y: number;   // tile grid y
  z: number;   // zoom level
  url: string;
  imageData?: ImageBitmap;  // cached in memory
}

Client-Only State

interface MapClientState {
  viewport: Viewport;
  mode: 'explore' | 'search' | 'directions' | 'navigation';
  searchQuery: string;
  searchResults: Place[];
  selectedPlace: Place | null;
  route: Route | null;
  activeRouteStep: number;
  userLocation: LatLng | null;
  savedPlaces: Place[];  // from server, cached
  isFollowingUser: boolean;  // navigation mode: auto-pan to user GPS
}

3.4 Interface / API

Tile API

GET /tiles/{z}/{x}/{y}?style=road&lang=en
→ image/png or image/webp

GET /tiles/vector/{z}/{x}/{y}.mvt
→ application/vnd.mapbox-vector-tile
(Vector tiles contain raw geometry, client styles them)

Tile URL generation:

// Slippy map tiles (OpenStreetMap convention)
function tileUrl(z, x, y) {
  return `https://tile.example.com/${z}/${x}/${y}.png`;
}

// Convert lat/lng to tile coordinates at zoom z
function latLngToTile(lat, lng, z) {
  const n = 2 ** z;
  const x = Math.floor((lng + 180) / 360 * n);
  const latRad = lat * Math.PI / 180;
  const y = Math.floor((1 - Math.log(Math.tan(latRad) + 1/Math.cos(latRad)) / Math.PI) / 2 * n);
  return { x, y, z };
}

Places API

GET /api/places/autocomplete?q=coffee&location=37.7,-122.4&radius=5000
→ {
    predictions: [{
      placeId: string,
      description: string,    // "Blue Bottle Coffee, Market St..."
      matchedSubstrings: [{offset: 0, length: 4}]
    }]
  }

GET /api/places/:placeId
→ { place: Place }

GET /api/places/nearby?lat=37.7&lng=-122.4&radius=1000&type=restaurant
→ { places: Place[], nextPageToken?: string }

Directions API

POST /api/directions
Body: {
  origin: LatLng | string,
  destination: LatLng | string,
  waypoints?: (LatLng | string)[],
  travelMode: 'DRIVE' | 'WALK' | 'TRANSIT' | 'BICYCLE',
  departureTime?: number,   // for transit schedules
  avoidTolls?: boolean,
  avoidHighways?: boolean
}
→ {
    routes: Route[],           // up to 3 route alternatives
    status: 'OK' | 'NO_ROUTE' | 'NOT_FOUND'
  }

3.5 Optimizations & Deep Dive

1. Tile Loading Strategy

On viewport change (pan/zoom):
1. Compute visible tiles for new viewport
2. Check memory cache (LRU, ~200 tiles)
3. Check browser cache (Cache-Control: max-age=86400)
4. Fetch missing tiles in parallel (up to 6 concurrent, per domain limit)
5. Prefetch adjacent tiles just outside viewport (overscan)
6. On zoom: immediately show scaled parent tile while loading new zoom tiles

Tile render priority:
  Center tiles > edge tiles
  Current zoom > adjacent zoom levels

Speculative loading for navigation:

User is traveling north → prefetch tiles ahead of route
Fetch next 2 route segments' tiles in advance

2. Polyline Encoding / Decoding

Routes can have thousands of lat/lng points. Google uses Encoded Polyline Algorithm to compress:

Raw: 37.7749,-122.4194, 37.7750,-122.4195, ...  (thousands of coords)
Encoded: "_p~iF~ps|U_ulLnnqC_mqNvxq`@"  (1/5 the size)

// Decode on client:
function decodePolyline(encoded: string): LatLng[] { ... }

3. Smooth Pan/Zoom Performance

// Use CSS transforms for instant visual feedback, re-render after settling
onPan(dx, dy) {
  // Immediately: CSS translate the tile container
  tileContainer.style.transform = `translate(${dx}px, ${dy}px)`;

  // Debounced: update viewport state, load new tiles
  debouncedUpdateViewport(dx, dy, 150);
}

// Use requestAnimationFrame for animation frames
// Use ResizeObserver to handle window resize
// Use IntersectionObserver to pause tile loading when tab not visible

4. Offline Maps (Service Worker + IndexedDB)

User action: "Download area for offline"

1. Calculate all tile URLs for area at zoom 10-15
2. Batch fetch tiles (show progress bar)
3. Store in IndexedDB via Cache API

Service Worker intercepts tile requests:
  match in offline store → serve from cache
  no match → network request (or show "offline" tile)

Place search offline:
  Store recently searched places in IndexedDB
  Fuzzy search against local store

5. Search UX — Autocomplete Debouncing

const [query, setQuery] = useState('');

const debouncedSearch = useCallback(
  debounce(async (q) => {
    if (q.length < 2) return;
    const results = await fetchAutocomplete(q);
    setResults(results);
  }, 150),  // 150ms debounce — fast enough to feel instant
  []
);

// Cancel in-flight request if new keystroke arrives
useEffect(() => {
  const controller = new AbortController();
  fetchAutocomplete(query, controller.signal);
  return () => controller.abort();
}, [query]);

6. URL as State

Encode viewport in URL for shareable links and browser back/forward:

/maps/@37.7749,-122.4194,15z        → lat, lng, zoom
/maps/place/Caltrain+Station/@...   → selected place
/maps/dir/San+Francisco/Palo+Alto   → directions
// Sync viewport ↔ URL without full page reload
history.replaceState(null, '', buildMapUrl(viewport, selectedPlace, route));
window.addEventListener('popstate', restoreFromUrl);

7. Accessibility Considerations

  • Non-map users: all features available without mouse (keyboard tab + Enter)
  • Screen reader: announce search results count, selected place details
  • High contrast mode for map overlay UI elements
  • Alternative to map: list view of search results with addresses


4. WhatsApp

4.1 Requirements

Clarifying Questions to Ask

  • Core features: 1:1 chat, group chat, voice/video calls, status?
  • Media sharing: images, videos, documents, voice messages?
  • Message delivery receipts (sent ✓, delivered ✓✓, read ✓✓blue)?
  • End-to-end encryption (E2EE) — how detailed to go?
  • Online/last seen presence?
  • Message reactions, replies, forwarding?
  • Web app only or desktop app (Electron)?

Functional Requirements

  • Conversation list with last message preview and unread count
  • Chat interface: message bubbles, timestamps, sender names (groups)
  • Send/receive: text, images, videos, documents, voice notes
  • Message status: sending → sent → delivered → read
  • Real-time delivery via WebSocket
  • Group chats (up to 256 members)
  • Message search (within chat or global)
  • Typing indicator ("John is typing...")
  • Online/offline presence & last seen

Non-Functional Requirements

  • Messages delivered in < 500ms on good network
  • Works on low-bandwidth connections (2G)
  • End-to-end encrypted (Signal Protocol)
  • Message queue: deliver messages when recipient comes online
  • Sync across multiple devices (WhatsApp Web + phone)

4.2 Architecture

┌──────────────────────────────────────────────────────────────────┐
│                          Browser Client                          │
│                                                                  │
│  ┌────────────────────────────────────────────────────────────┐  │
│  │                       View Layer                           │  │
│  │  ┌─────────────────┐       ┌──────────────────────────┐   │  │
│  │  │  Conversation   │       │     Chat Window           │   │  │
│  │  │  List           │       │  ┌──────────────────────┐ │   │  │
│  │  │  (left panel)   │       │  │  Virtual Message List│ │   │  │
│  │  │  - sorted by    │       │  │  (scroll + load more)│ │   │  │
│  │  │    recency      │       │  └──────────────────────┘ │   │  │
│  │  │  - unread badge │       │  ┌──────────────────────┐ │   │  │
│  │  │  - last preview │       │  │  Typing Indicator    │ │   │  │
│  │  │                 │       │  └──────────────────────┘ │   │  │
│  │  │                 │       │  ┌──────────────────────┐ │   │  │
│  │  │                 │       │  │  Message Input Bar   │ │   │  │
│  │  │                 │       │  │  (text, attach, mic) │ │   │  │
│  │  └─────────────────┘       │  └──────────────────────┘ │   │  │
│  │                            └──────────────────────────┘   │  │
│  └────────────────────────────────────────────────────────────┘  │
│                                │                                  │
│  ┌─────────────────────────────▼──────────────────────────────┐  │
│  │                     Chat Controller                         │  │
│  │  (Message send, media upload orchestration, encryption)     │  │
│  └───────────────────┬─────────────────────┬───────────────────┘  │
│                      │                     │                      │
│  ┌───────────────────▼──────┐  ┌───────────▼─────────────────┐   │
│  │  Chat Store (Client)     │  │  WebSocket Manager           │   │
│  │  Conversations, Messages │  │  (connection, reconnect,     │   │
│  │  Contacts, Media cache   │  │   heartbeat, queue)          │   │
│  └──────────────────────────┘  └─────────────────────────────┘   │
└──────────────────────────────────────────────────────────────────┘
                         │ WebSocket (wss://)
              ┌──────────▼─────────────────────────────┐
              │  Messaging Server                       │
              │  (WebSocket handler + message router)   │
              │  ← Connected to Message Queue, DB →     │
              └─────────────────────────────────────────┘

Multi-Device Sync

WhatsApp Web works by mirroring your phone. Modern architecture (multi-device) syncs via a shared encrypted message store on server, no phone dependency.


4.3 Data Model

interface Conversation {
  id: string;
  type: 'direct' | 'group';
  participants: UserSummary[];
  lastMessage: MessagePreview;
  unreadCount: number;
  updatedAt: number;
  // Group-only fields
  groupName?: string;
  groupAvatar?: string;
  groupDescription?: string;
  adminIds?: string[];
}

interface Message {
  id: string;            // client-generated UUID (for dedup)
  conversationId: string;
  senderId: string;
  type: 'TEXT' | 'IMAGE' | 'VIDEO' | 'AUDIO' | 'DOCUMENT' | 'STICKER' | 'LOCATION';
  content: MessageContent;
  timestamp: number;
  status: 'SENDING' | 'SENT' | 'DELIVERED' | 'READ' | 'FAILED';
  replyTo?: string;      // message id being replied to
  reactions?: Reaction[];
  isDeleted?: boolean;
  isEdited?: boolean;
  editedAt?: number;
}

type MessageContent =
  | { type: 'TEXT'; text: string }
  | { type: 'IMAGE'; url: string; thumbnailUrl: string; width: number; height: number; caption?: string }
  | { type: 'VIDEO'; url: string; thumbnailUrl: string; duration: number; caption?: string }
  | { type: 'AUDIO'; url: string; duration: number; waveform: number[] }
  | { type: 'DOCUMENT'; url: string; fileName: string; fileSize: number; mimeType: string }
  | { type: 'LOCATION'; lat: number; lng: number; label?: string }

interface MessagePreview {
  id: string;
  text: string;          // truncated preview text
  type: Message['type'];
  timestamp: number;
}

interface UserPresence {
  userId: string;
  status: 'online' | 'offline';
  lastSeen?: number;     // unix timestamp if offline
  isTyping?: boolean;    // in specific conversation
}

interface Reaction {
  emoji: string;
  userIds: string[];
  count: number;
}

Client-Only State

interface ChatDraft {
  [conversationId: string]: string;  // per-conversation draft text
}

interface UploadState {
  file: File;
  progress: number;        // 0-100
  uploadedUrl?: string;
  status: 'pending' | 'uploading' | 'done' | 'error';
  previewUrl: string;      // local object URL for immediate preview
}

interface PendingMessages {
  [messageId: string]: Message;  // messages sent but not yet acked by server
}

4.4 Interface / API

WebSocket Protocol (Primary Communication)

// Client → Server messages
type ClientMessage =
  | { type: 'SEND_MESSAGE'; message: Message }
  | { type: 'MESSAGE_ACK'; messageId: string; conversationId: string }
  | { type: 'READ_RECEIPT'; conversationId: string; upToMessageId: string }
  | { type: 'TYPING_START'; conversationId: string }
  | { type: 'TYPING_STOP'; conversationId: string }
  | { type: 'PING' }  // heartbeat every 30s

// Server → Client messages
type ServerMessage =
  | { type: 'NEW_MESSAGE'; message: Message }
  | { type: 'MESSAGE_STATUS_UPDATE'; messageId: string; status: Message['status'] }
  | { type: 'TYPING'; conversationId: string; userId: string; isTyping: boolean }
  | { type: 'PRESENCE_UPDATE'; userId: string; status: string; lastSeen?: number }
  | { type: 'PONG' }
  | { type: 'SYNC_STATE'; conversations: Conversation[] }  // on reconnect

// WebSocket URL
wss://api.example.com/ws?token=<jwt>

REST APIs (for initial load and history)

GET /api/conversations?cursor=&size=20
→ { conversations: Conversation[], nextCursor: string | null }

GET /api/conversations/:id/messages?cursor=&size=50&direction=before
→ { messages: Message[], hasMore: boolean, cursor: string }

POST /api/media/upload
Body: FormData { file, type, conversationId }
→ { url: string, thumbnailUrl?: string, duration?: number }

GET /api/contacts?q=john&size=20
→ { contacts: UserSummary[] }

POST /api/conversations
Body: { participantIds: string[], type: 'direct' | 'group', groupName?: string }
→ { conversation: Conversation }

4.5 Optimizations & Deep Dive

1. WebSocket Connection Management

class WebSocketManager {
  private ws: WebSocket;
  private reconnectDelay = 1000;
  private pingInterval: number;
  private messageQueue: ClientMessage[] = [];  // buffer while disconnected

  connect() {
    this.ws = new WebSocket(`wss://api.example.com/ws?token=${getToken()}`);
    this.ws.onopen = () => {
      this.reconnectDelay = 1000;  // reset backoff
      this.drainQueue();           // send buffered messages
      this.startHeartbeat();
    };
    this.ws.onclose = () => this.scheduleReconnect();
  }

  scheduleReconnect() {
    setTimeout(() => {
      this.reconnectDelay = Math.min(this.reconnectDelay * 2, 30000);  // exponential backoff, cap 30s
      this.connect();
    }, this.reconnectDelay);
  }

  startHeartbeat() {
    this.pingInterval = setInterval(() => {
      this.send({ type: 'PING' });
    }, 30000);  // ping every 30s to keep connection alive
  }
}

2. Message Send Flow (Optimistic UI)

User hits "Send":

1. Assign client-generated messageId (UUID)
2. Immediately add message to UI with status: SENDING
3. Add to pendingMessages store
4. Send via WebSocket: { type: 'SEND_MESSAGE', message }
5. Server ACKs → update status: SENT, remove from pending
6. Recipient online: DELIVERED status
7. Recipient opens chat: READ status (blue ticks)

On WebSocket disconnect:
  → Queue message in pendingMessages
  → Drain queue on reconnect
  → Server deduplicates by message.id

3. Virtual Message List (Performance)

Chat histories can have thousands of messages. Use virtualized list:

Techniques:
- Only render ~20 visible messages + overscan buffer
- Dynamic row heights (text wraps, images vary)
- Maintain scroll position when prepending older messages
  (sticky bottom for new msgs, preserve position when loading history)
- Intersection Observer to trigger "load older messages" when
  user scrolls near top
- Group consecutive messages from same sender (no repeated avatar)

Challenge: "Scroll to bottom" on new message
  → Only auto-scroll if user is already at bottom (don't interrupt reading history)
  → Show "N new messages ↓" badge if user has scrolled up

4. End-to-End Encryption (Signal Protocol Overview)

Key concepts (for interview context):

Double Ratchet Algorithm:
  - Combines Diffie-Hellman key exchange + symmetric ratchet
  - Each message encrypted with unique key (forward secrecy)
  - Compromising one message key doesn't expose past/future messages

X3DH (Extended Triple Diffie-Hellman):
  - Used for initial key exchange between users
  - Allows asynchronous key exchange (recipient offline)

Client responsibilities:
  - Generate key pairs locally (never send private key to server)
  - Store encrypted message keys in IndexedDB
  - Decrypt messages only in browser (server sees ciphertext only)
  - Display lock icon 🔒 to indicate E2EE

Server stores:
  - Ciphertext only
  - Public keys for key exchange
  - NOT decryption keys

5. Media Messages UX

Image send flow:
1. User selects image
2. Show local preview immediately (createObjectURL)
3. Upload to CDN in background (multipart, show progress bar)
4. On upload complete: send message with CDN URL via WebSocket
5. Recipient receives message → lazy-load image from CDN

Low-bandwidth optimizations:
  - Show blurhash placeholder while image loads
  - Progressive JPEG / WebP with quality tiers
  - Thumbnail-first: load 10% size preview, then full res
  - Video: don't autoplay, show thumbnail + duration
  - Auto-compress images > 5MB before upload

Voice messages:
  - Record with MediaRecorder API
  - Show waveform visualization (pre-computed amplitude data)
  - Scrubbing support: click to seek
  - Playback speed toggle (1x, 1.5x, 2x)

6. Typing Indicators — Throttle & Debounce

// Don't send typing event on every keystroke — throttle to 1/3s
const sendTypingStart = throttle(() => {
  ws.send({ type: 'TYPING_START', conversationId });
}, 3000);

// Send typing stop 2s after last keystroke
const sendTypingStop = debounce(() => {
  ws.send({ type: 'TYPING_STOP', conversationId });
}, 2000);

onKeyDown = () => {
  sendTypingStart();
  sendTypingStop();
};

// Server auto-expires typing state after 5s (in case TYPING_STOP never arrives)

7. Offline Support & Message Queuing

IndexedDB schema (persisted across sessions):
  - conversations (IDBObjectStore)
  - messages (IDBObjectStore, indexed by conversationId + timestamp)
  - pendingMessages (IDBObjectStore — unsent messages)
  - mediaCache (IDBObjectStore — recently viewed images)

On load:
  1. Render conversations from IndexedDB immediately (instant UI)
  2. Connect WebSocket
  3. Request sync since last seen timestamp
  4. Merge server data with local cache
  5. Drain pending messages queue

Service Worker:
  - Cache app shell (HTML, JS, CSS) for instant loads
  - Intercept media CDN requests → serve from cache

8. Group Chat Optimizations

Efficient unread counting:
  - Server stores per-user watermark (lastReadMessageId per conversation)
  - Unread = messages after watermark
  - Batch READ_RECEIPT when user opens conversation

Member list:
  - Don't load all 256 members upfront
  - Load on-demand when user opens member list
  - Paginate: 20 at a time

Message fan-out (server concern, worth mentioning):
  - Server sends to each group member's WebSocket connection
  - For large groups: message queue (Kafka) → individual push

Summary Cheat Sheet

Product Hardest Problem Key Technique
Google Sheets Rendering 1M rows, formula recalc Canvas virtualization, Web Worker formula engine, OT for collab
PayPal Security & no double-charge Idempotency keys, pessimistic UI, PCI tokenization, httpOnly cookies
Google Maps Tile loading, smooth pan/zoom Tile CDN + LRU cache, vector tiles, WebGL, polyline encoding
WhatsApp Real-time delivery, offline WebSocket + exponential backoff, IndexedDB queue, Signal Protocol E2EE

Built with the RADIO Framework: Requirements → Architecture → Data Model → Interface → Optimizations

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment