ESC

Type to search the knowledge base.

A/B Testing Frontend Design

Frontend system design for A/B experiments — assignment, flicker control, flags, metrics, and rollout tradeoffs.

intermediate4 min read
  • system-design
  • interview
  • architecture
  • experiments

Scope the problem

In scope (frontend):

  • Experiment assignment delivery to the client
  • Preventing UI flicker (FOOC — flash of original content)
  • Wiring variants into components safely
  • Exposure logging and conversion events
  • Interaction with SSR/CSR and feature flags

Out of scope unless asked: experiment stats engine, sequential testing math, full CDP warehouse design.

Assumptions to state: multi-variant A/B (not only on/off), logged-in + anonymous users, web-first with SSR possible.

Requirements & metrics

Type Examples
Functional Assign variant, render correct UI, track exposure once
Non-functional No layout flicker, low assignment latency, privacy-safe ids
Product Lift on primary metric without harming Core Web Vitals

High-level architecture

┌──────────────────────────────────────────────────────────┐
│ Edge / App server                                        │
│  - resolve experiments for request (cookie / user id)    │
│  - embed bootstrap payload in HTML                       │
├──────────────────────────────────────────────────────────┤
│ Client Experiment SDK                                    │
│  - getVariation(key)  - track(event)  - cache assignments│
├──────────────┬───────────────────────┬───────────────────┤
│ UI variants  │ Analytics bridge      │ Feature flags     │
│ components   │ exposure + convert    │ (related system)  │
└──────────────┴───────────────────────┴───────────────────┘

Assignment model

type ExperimentKey = string;
type Variant = "control" | "treatment" | string;

type Assignment = {
  exp: ExperimentKey;
  variant: Variant;
  expVersion: number;
};

type Bootstrap = {
  assignments: Assignment[];
  // sticky id for anonymous: from first-party cookie
  subjectId: string;
};

Stickiness: same subject should see the same variant for experiment lifetime. Prefer server-set first-party cookie or logged-in user id. Don’t rely on localStorage alone for SSR-critical experiments (not available on first request).

Where assignment runs

Placement Pros Cons
Edge / SSR No flicker; SEO-safe HTML Coupling; cache key fragmentation
Client SDK Simple SPA Flicker risk; delayed exposure
Hybrid SSR critical exp; client rest Complexity

Interview default: critical above-fold experiments assigned at edge/SSR; non-critical client-side.

Flicker control

  1. Inline bootstrap in HTML before app JS: window.__EXPS__ = …
  2. CSS/class on <html> for major layout variants (e.g. exp-nav-b) applied by tiny inline script
  3. Block render only as last resort (hurts LCP)
  4. For SPA navigations, read from memory cache of assignments

Anti-pattern: fetch /assign after hydration then swap hero CTA — users and metrics both suffer.

Component integration

function CheckoutButton() {
  const v = useExperiment("checkout_cta_copy");
  const label =
    v === "treatment" ? "Complete purchase" : "Buy now";

  useExposure("checkout_cta_copy"); // once per session/assignment

  return <button type="button">{label}</button>;
}

Patterns:

  • Variant components — CheckoutA / CheckoutB via map
  • Config-driven — copy/color from payload (safer than shipping dead code for every test)
  • Avoid sprawling if (exp) trees without ownership

Dead code from concluded experiments becomes tech debt — require removal SLAs.

Data, caching, logging

Layer What Strategy
Cookie / header subject id + sticky assignments HttpOnly where possible; careful size
HTML bootstrap active assignments Per-user; affects CDN cache keys
Memory SDK cache Session lifetime
Analytics exposure, convert Dedupe exposure; schema versioned

Exposure: log when user actually saw the variant UI (not merely assigned). Dedupe per exp version per session.

Conversions: reuse product analytics with experiment context dimensions attached at event time.

Privacy

  • Prefer first-party ids
  • Honor DNT/consent modes — hold assignment or use non-personalized defaults
  • Don’t put PII in experiment keys

Performance

  • Assignment payload tiny (hundreds of bytes)
  • Avoid extra RTT on critical path — bootstrap with document
  • CDN: cache anonymous pages carefully when experiments fragment HTML (Vary / cache key includes exp bucket or only run client-side)
  • Don’t load heavy treatment bundles for users in control — split code by variant when bundles differ a lot

Feature flags vs experiments

Feature flags A/B experiments
Goal rollout / kill switch measure causal lift
Traffic % ramp randomized assignment
Analysis operational statistical

Often one platform powers both; keep semantic separation in client APIs (useFlag vs useExperiment).

Tradeoffs

  1. SSR assignment vs edge cache hit rate — personalization fragments cache
  2. Client simplicity vs flicker
  3. Config-driven UI vs fully separate trees
  4. Many simultaneous experiments — interaction effects; mutually exclusive layers
  5. Exposure definition — view vs interaction affects stats

Failure modes

  • SDK down → fail open to control
  • Partial bootstrap → don’t re-randomize client-side differently than server
  • Long-running experiments with code rot

Interview close

Recap: sticky assignment, anti-flicker bootstrap, exposure vs conversion, cache implications, cleanup of winning variant. Mention sample size only at high level unless interviewer is stats-heavy.

Further reading