A/B Testing: GrowthBook Controls Who Sees What

When your chain restaurant launches a new dish, it doesn't roll it out at all locations simultaneously — it first appears on the menu at a few pilot locations, and after the feedback looks good it spreads to all (feature flags). At the same time, to test a new ordering UI, members are randomly split into two groups: group A uses UI A, group B uses UI B; after a month you check which group had higher order rates (A/B experiment). What appears on the menu is determined by HQ configuration — the server (the code) just checks "is this dish available here today?", no judgment needed.

These two capabilities in Claude Code are provided by GrowthBook. src/services/analytics/growthbook.ts uploads user attributes at initialization time, which determine "what version this user should see":

// src/services/analytics/growthbook.ts
export type GrowthBookUserAttributes = {
  id: string
  sessionId: string
  deviceID: string
  platform: 'win32' | 'darwin' | 'linux'
  organizationUUID?: string
  accountUUID?: string
  userType?: string
  subscriptionType?: string   // Subscription type (free/Pro/Max) — like membership tier
  rateLimitTier?: string      // API rate limit tier
  email?: string
  appVersion?: string
  github?: GitHubActionsMetadata
}

subscriptionType and rateLimitTier are the key fields for differentiated product experiences — like a Pro user getting early access to an experimental feature, similar to a restaurant's gold-card members seeing the hidden menu. These decisions all live in GrowthBook's config; no if (user.isPro) { ... } needs to be written in the code.

Cache May Be Stale

This config isn't fetched in real time. Like menus printed every morning and distributed to each branch — servers consult that physical copy all day. If a dish sells out in the morning, the menu still shows it; afternoon customers order it and only then discover it's unavailable. GrowthBook periodically pulls the latest config from the server, but between pulls, the code reads the last cached value. This is exactly why the function name deliberately includes the _CACHED_MAY_BE_STALE suffix — reminding callers "what you're getting might not be current." For feature flags this is completely acceptable: config changes don't need millisecond-level propagation; taking effect at the next session or next pull is sufficient.

Experiment Exposure Deduplication

Your restaurant's "new dish satisfaction survey" rules specify that the same member only needs to be counted as "participated" once, regardless of how many times they visit. If every QR code scan repeated the count, a frequent customer might be counted dozens of times, completely distorting the survey data.

loggedExposures does the same thing — records "which experiments have already been exposure-counted," ensuring the same experiment only logs "entered the experiment group" once per session:

// src/services/analytics/growthbook.ts
const loggedExposures = new Set<string>()