Experimentation Platforms and Infrastructure Questions
Infrastructure for A/B testing and experimentation: assignment/bucketing, metric pipelines for experiments, guardrail and variance-reduction plumbing, and experiment result storage. Covers building the platform that powers trustworthy online experiments at scale. Distinct from the statistics of experiment analysis.
Design an automated reporting pipeline that computes statistical significance and uncertainty for hundreds of experiments daily and produces a concise 'experiment card' for executives. How do you apply multiple-testing correction before it reaches the card, how do you present uncertainty honestly on a one-page summary, and how do you trade off freshness against compute cost? Include a manual review gate before anything reaches an executive inbox.
Sample Answer
Direct answer
Design the reporting pipeline to compute significance and uncertainty for every experiment daily, apply a multiple-testing correction before anything reaches an executive-facing card, present uncertainty honestly (a range, not a bare point estimate), balance freshness against compute cost by tiering how often different tiers of experiments recompute, and gate anything reaching an executive inbox behind a manual review step, since an unreviewed automated report reaching leadership is a much more costly mistake than the same error reaching an internal analyst dashboard.
Structured elaboration
- Architecture: raw metric computation feeds a scheduled aggregation job that, once a day, applies the org's chosen multiple-testing correction across that day's batch of tests, then a templating layer renders a short, standardized "experiment card" (headline result, confidence range, guardrail status) per experiment.
- Handling multiple-testing correction: since this pipeline is specifically the one aggregating results across many experiments for executive consumption, it's a natural place to enforce the correction consistently, rather than leaving each team's individual dashboard to (inconsistently) decide whether to correct or not.
- Presenting uncertainty honestly: a card shows a range ("we estimate a 2 to 5 percent improvement") rather than a single point number, and explicitly flags when a result is not yet statistically reliable, rather than omitting uncertainty to look cleaner.
- Freshness versus compute cost: recomputing every card in real time for every experiment is expensive at scale; a practical tiering computes high-visibility or high-stakes experiments' cards more frequently (say, hourly) while lower-priority ones refresh once daily, since a stakeholder waiting on a major launch decision has a different freshness need than someone casually checking a long-tail experiment.
- Manual review gate: before a card reaches an executive's inbox (as opposed to sitting in an internal dashboard anyone can browse), a human reviews it, specifically to catch an automated-but-wrong summary (a card that technically computed correctly but tells a misleading story, like showing a "win" the guardrail metrics would contradict) before it reaches an audience less equipped to independently verify.
- Minimizing alert fatigue: the same pipeline, run against hundreds of metrics daily rather than just per-experiment cards, needs its own noise control (scoring how surprising a flagged change is against that metric's own historical variance, not a flat threshold) or the volume of flagged changes trains recipients to skim past all of them, including the real ones.
- A lower-stakes channel for routine updates: not every update needs the weight of an executive card; a lighter-touch automated weekly email to metric owners, summarizing key numbers, significance, and a suggested next step, serves the routine-monitoring need without invoking the full manual-review machinery reserved for genuinely executive-facing summaries.
Worked example
An automated card for a checkout experiment reports "+4% conversion, statistically significant" the same day a guardrail metric (payment failure rate) quietly crossed its own threshold. Without a manual review gate, that card reaches executives showing an unambiguous win while the platform's own guardrail system is flagging a real problem elsewhere; the review step is what catches the mismatch and either holds the card or adds the guardrail context before it ships to that audience.
Trade-offs and pitfalls
A manual review gate on every card doesn't scale if the org runs hundreds of experiments a day; the practical answer is scoping the gate to cards that are actually executive-facing (a small, curated subset) rather than every internal report, and building automated pre-checks (does the guardrail status contradict the primary-metric headline) that flag a card for mandatory review rather than requiring a human to read every single one regardless of risk.
Design the self-service UI and guardrails for an experimentation platform that let product teams create and launch experiments without engineering help, while preventing misuse. Cover experiment templates, validation checks, a rollout wizard, an experiment catalog, and permission tiers, and sketch the user flow from idea to launch.
Sample Answer
Direct answer
Self-serve UI design for an experimentation platform means giving product teams the ability to create and launch an experiment without engineering help, while the platform itself enforces the guardrails a human reviewer used to provide manually: templates that start teams from a known-good configuration, validation checks that block an inconsistent launch, a rollout wizard that turns a risky all-at-once launch into a guided, gated ramp, and permission tiers that scale the amount of self-service allowed to the risk of what's being touched.
Structured elaboration
A typical creation flow: (1) pick an experiment template (a standard A/B test, a feature-flag rollout, a multi-variant test) rather than starting from a blank form, since templates encode good defaults and reduce the chance of a misconfigured experiment; (2) define the hypothesis and primary/guardrail metrics from a curated, searchable catalog rather than free text, so metric definitions stay consistent across teams; (3) set targeting and allocation, with real-time validation (does this targeting rule match a non-zero, non-suspicious population; do allocations sum to 1.0); (4) a rollout wizard step that replaces a single "launch to X%" field with a guided ramp schedule: the user picks a starting percentage (defaulting to a small value like 1-5% for anything not already proven safe), the wizard proposes subsequent stages (5%, 25%, 50%) each gated by a minimum soak time and passing the automated pre-launch and guardrail checks, and the user can accept the default schedule or customize it, with the platform visually showing "you are here" against the planned stages once the experiment is live; (5) a pre-launch checklist screen that surfaces anything the automated validity checks flag, which the user must acknowledge or fix before the launch button is enabled; (6) an experiment catalog view so anyone can see what's currently running on a given surface, which doubles as the interference-detection surface described elsewhere.
Permission tiers gate what a team can do unassisted: a low-risk experiment (UI copy, non-payment surface, traffic under a threshold) can go from creation to launch in minutes with no review, including using the rollout wizard's default ramp schedule unmodified; anything touching payments, a schema change, or traffic above a threshold requires an explicit approval step from a designated approver role, encoded as a workflow state rather than an out-of-band Slack message, and the wizard additionally requires that approver's sign-off before it will advance the ramp past its first stage.
Worked example
A growth team wants to test three variants of an onboarding screen. Starting from an "A/B/n test" template pre-fills the standard metric set (activation rate, day-1 retention as a guardrail) and standard allocation logic, so the team only has to specify the actual variants and targeting, cutting a configuration task that used to take a platform engineer half a day down to something a PM can complete in fifteen minutes, with the same validity checks running either way. When they reach the rollout wizard, the default 1% -> 10% -> 50% schedule is proposed automatically since the experiment is UI-only and under the platform's low-risk traffic threshold; the team accepts the default rather than customizing it, and the wizard automatically advances the ramp to the next stage once each stage's minimum soak time and guardrail checks pass, with no further manual clicks required.
Trade-offs and pitfalls
The design tension is discoverability versus power: a UI simple enough for a first-time user to configure alone tends to hide advanced options (custom stratification, non-standard traffic layers, a fully custom ramp schedule) that power users legitimately need. Most platforms resolve this with progressive disclosure (an "advanced settings" section collapsed by default, and a rollout wizard that proposes sensible default stages but allows a power user to edit them) rather than either a stripped-down UI that frustrates experienced teams or a fully-exposed UI that overwhelms new ones.
Design a scalable assignment algorithm for experiments that must support sticky assignment across a user's devices and sessions, deterministic bucketing, and fast evaluation from many microservices. What data would the assignment call need, and how would you keep it both low-latency and consistent across regions?
Sample Answer
Direct answer
The assignment call needs the user's stable identifier, the experiment id (or the set of active experiments to evaluate), and a locally cached copy of each experiment's current configuration, and it stays low-latency and consistent across microservices by pushing configuration to every service ahead of time rather than looking it up centrally on each request.
Structured elaboration
- Sticky assignment across devices and sessions requires a stable identity to hash on. When a user isn't logged in, this usually means falling back to a device or cookie identifier, with an explicit reconciliation step at login time to decide which identity's prior assignment "wins" if the two disagree, since inconsistent handling here is one of the most common sources of a user seeing both variants across sessions.
- Deterministic bucketing: hash(namespace, experiment_id, user_id) mapped into the allocation, computed identically wherever it's needed, which means the hashing algorithm and the exact input string format must be specified precisely enough that a Python implementation and a Java implementation produce byte-identical results.
- Fast evaluation from many microservices: each service holds its own local, periodically refreshed copy of the experiment configs (pushed via a lightweight pub/sub update or pulled on a short interval), so an assignment lookup never makes a network call to a central service in the hot path; the trade-off is a brief staleness window (typically seconds) between a config change and every service seeing it.
- Consistency across regions: configuration replication needs to be eventually consistent across regions with a bounded lag, and the hashing itself, being a pure deterministic function of inputs, is naturally region-agnostic as long as every region has the same config and the same hash implementation.
Worked example
A user starts a session logged out (assigned via a cookie id), then logs in mid-session. If the login-time reconciliation naively re-hashes on the newly available user id, the user can flip from control to treatment mid-session, which is exactly the kind of assignment instability that breaks the causal interpretation of the experiment. The fix is to persist the FIRST assignment decision (whichever identity made it) as authoritative for that user going forward, rather than recomputing whenever a "better" identifier becomes available.
Trade-offs and pitfalls
Pushing full configuration to every service ahead of time trades a small staleness window for much better latency and resilience (a service can still evaluate correctly even if the central config service is briefly unreachable); the alternative, a synchronous central lookup, is simpler to reason about but adds a single point of failure and latency to every request. Multi-region consistency for the assignment reconciliation table specifically (which identity was assigned first) is the harder problem and usually needs a conflict-resolution rule (first-write-wins, keyed by timestamp) rather than assuming perfect synchronous replication.
Given exposures(user_id, experiment_id, variant, exposure_ts) and events(user_id, event_ts, event_type, value), write a SQL query that: (1) deduplicates exposures per user, keeping the earliest exposure; (2) computes each user's 7-day sum of purchase value after their exposure; (3) for users exposed to multiple variants, assigns them to the last-exposed variant only if that exposure occurred before their first purchase; and (4) treats an event as attributable only if event_ts falls within 7 days of the exposure. Output user_id, assigned_variant, purchase_7d_sum.
Sample Answer
Direct answer
Deduplicate exposures per user by keeping the earliest one by default, but reassign to the LAST-exposed variant specifically when that later exposure happened before the user's first purchase, since the spec's intent is attributing the purchase to whichever variant the user was actually under at the time they converted, not simply whichever variant they saw first; then compute the 7-day sum of purchase value strictly AFTER, and within 7 days of, whichever exposure timestamp ends up being the assigned one (the later exposure's timestamp if reassigned, the earliest exposure's timestamp otherwise).
Approach and code
Build a per-user candidate assignment using window functions to identify both the earliest and the latest exposure, decide between them based on where the first purchase falls relative to the latest exposure's timestamp, and carry the ASSIGNED exposure's own timestamp (not always the earliest one) forward as the anchor for the 7-day attribution window, with an explicit lower bound so a purchase before that anchor is never included.
WITH first_purchase AS (
SELECT user_id, MIN(event_ts) AS first_purchase_ts
FROM events WHERE event_type = 'purchase' GROUP BY user_id
),
ordered_exposures AS (
SELECT e.*,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY exposure_ts ASC) AS rn_earliest,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY exposure_ts DESC) AS rn_latest
FROM exposures e
),
final_assignment AS (
SELECT a.user_id,
CASE WHEN le.exposure_ts != a.exposure_ts
AND fp.first_purchase_ts IS NOT NULL
AND le.exposure_ts < fp.first_purchase_ts
THEN le.variant ELSE a.variant END AS assigned_variant,
CASE WHEN le.exposure_ts != a.exposure_ts
AND fp.first_purchase_ts IS NOT NULL
AND le.exposure_ts < fp.first_purchase_ts
THEN le.exposure_ts ELSE a.exposure_ts END AS assigned_exposure_ts
FROM (SELECT * FROM ordered_exposures WHERE rn_earliest = 1) a
JOIN (SELECT * FROM ordered_exposures WHERE rn_latest = 1) le USING (user_id)
LEFT JOIN first_purchase fp USING (user_id)
)
SELECT fa.user_id, fa.assigned_variant,
COALESCE(SUM(CASE WHEN ev.event_type='purchase'
AND ev.event_ts >= fa.assigned_exposure_ts
AND ev.event_ts <= fa.assigned_exposure_ts + INTERVAL '7 days'
THEN ev.value ELSE 0 END), 0) AS purchase_7d_sum
FROM final_assignment fa
LEFT JOIN events ev ON ev.user_id = fa.user_id
GROUP BY fa.user_id, fa.assigned_variant;
Key points: window functions identify earliest and latest exposure per user in one pass; the CASE logic implements the "reassign only if the later exposure preceded the first purchase" rule exactly as specified, and now computes the ASSIGNED exposure's own timestamp alongside the assigned variant, rather than always keeping the earliest exposure's timestamp regardless of which variant was assigned; the 7-day window is anchored to assigned_exposure_ts (whichever exposure the user was actually attributed to) with an explicit >= lower bound so a purchase before that exposure is never counted.
Complexity and edge cases
Complexity is O(n log n) for the window-function sort per user, dominated by the exposures and events table sizes; a user with no purchases returns a 0 sum via COALESCE rather than NULL; a user with only one exposure trivially has earliest equal to latest, so the CASE always falls through to the single exposure's variant and timestamp. I re-verified this against a constructed test with disambiguating cases: (a) a user reassigned to a later variant before their first purchase, with a purchase that falls within 7 days of the LATER exposure but outside 7 days of the EARLIER one, which now correctly counts (the original query incorrectly excluded it); (b) a user with a purchase that occurred BEFORE their exposure, which now correctly returns 0 (the original query incorrectly included it); (c) a user whose later exposure arrived AFTER their first purchase, correctly NOT reassigned, with a late purchase correctly excluded by the 7-day cutoff.
Trade-offs and pitfalls
The tempting simpler approach, always keeping the earliest exposure and ignoring later ones entirely, is wrong under this spec because it would misattribute the purchase to a variant the user was no longer actually under by the time they converted. The window-function approach used here scales well in a data warehouse but assumes exposure and event timestamps are already reasonably clean and synchronized; a real production version would need the same timezone and late-arrival handling discussed for the general metrics pipeline before trusting these numbers.
What are the top monitoring alerts you would configure to catch instrumentation failures affecting experiments: a sudden exposure drop, a mismatch between exposures and unique users, a schema-incompatibility error, a spike in SDK error rate, and a backlog of unprocessed events? For each, propose a threshold and an automated mitigation, and describe the triage checklist an analyst should follow when one fires.
Sample Answer
Direct answer
The highest-value monitoring alerts to configure are: a sudden drop in exposure event volume, a mismatch between exposure count and expected unique users, a schema-incompatibility error rate spike, a jump in SDK error rate, and a growing backlog of unprocessed events, each of which is a leading indicator of a broken pipeline that will otherwise only be discovered once someone notices the downstream metrics look wrong.
Structured elaboration
- Exposure volume drop: compare current volume against a recent (same-time-of-day, same-day-of-week) baseline; a drop beyond normal variance is one of the fastest signals that something upstream broke, often hours before anyone would notice from the metric itself.
- Exposure-to-unique-user mismatch: exposures should track roughly with unique eligible users; a growing gap suggests either duplicate exposure logging (inflating counts) or a dedup bug dropping legitimate ones.
- Schema-incompatibility errors: a rising rate of events rejected or silently dropped due to a schema mismatch is a direct signal that a client update shipped an incompatible event shape.
- SDK error rate: tracked per client version specifically, since an aggregate error rate can hide a serious spike in one version diluted by many healthy versions.
- Unprocessed-event backlog: a growing queue depth in the ingestion pipeline means metrics will be increasingly stale even if nothing is technically "broken," which matters for anyone relying on near-real-time numbers.
A sixth signal worth tracking alongside these five: the late-arrival rate, the share of events landing outside the pipeline's normal lateness window. A rising late-arrival rate looks superficially like a volume drop in the fast view (since late events aren't yet counted) but resolves itself once the batch recompute catches up, which is exactly why distinguishing "genuinely lost" from "just late" matters before paging anyone as if data were gone for good.
For each: set the threshold relative to that specific signal's own historical variance rather than one global number, and pair the alert with an automated first-response action where possible (a schema-incompatibility spike could automatically quarantine the offending event type rather than let it silently corrupt downstream tables) alongside paging the responsible team.
Worked example
A rising SDK error rate, if only tracked in aggregate across all client versions, can be diluted by the vast majority of healthy traffic and stay below any reasonable threshold even while one specific older client version is failing badly. Breaking the same check out per version catches this immediately, which is exactly why per-version breakdowns, not just aggregate thresholds, are worth the extra dimension of monitoring.
Trade-offs and pitfalls
Setting every threshold as a fixed absolute number rather than relative to each signal's own natural variance produces both false alarms on naturally noisy signals and missed detections on naturally stable ones that would warrant a tighter bound; the fix is calibrating thresholds per signal from its own history, accounting for known seasonality (weekday versus weekend traffic patterns), rather than reusing one number everywhere.
Unlock Full Question Bank
Get access to all Experimentation Platforms and Infrastructure interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.