Run conflicting experiments on one surface without interference
Put two experiments that touch the same surface in the same universe so a user is assigned to at most one — and neither confounds the other.
Two teams want to experiment on the checkout page at the same time — one on button copy, one on the layout. Run them independently and a user can land in both, so the layout change quietly inflates or masks the copy result and vice-versa. Now neither experiment can be trusted: the lift you measure on one is partly the other.
The fix is a universe with mutual exclusion: put both experiments in the same universe and a user is assigned to at most one of them. They can't confound each other because no user is ever in both.
The primitive. A universe is the world an experiment lives in; every experiment belongs to exactly one. A universe with mutual exclusion guarantees at most one experiment in it assigns each user — so two experiments touching the same surface can't overlap.
Put both experiments in one universe
Create a universe for the surface, then create both experiments inside it:
# one universe for the checkout surface, 5% global holdout
shipeasy universes create checkout --holdout 0,499
# both experiments live in it — a user gets at most one
shipeasy experiments create checkout-copy --universe checkout
shipeasy experiments create checkout-layout --universe checkoutBucketing is deterministic per universe — hash(universe_salt, user_id) — so the assignment is stable across processes and deploys. In code you read each experiment as usual; the universe handles the "at most one" guarantee:
import { configure, Client } from "@shipeasy/sdk/server";
configure({ apiKey: process.env.SHIPEASY_SERVER_KEY! });
const flags = new Client({ user_id });
const copy = flags.getExperiment("checkout-copy", { variant: "control" });
const layout = flags.getExperiment("checkout-layout", { variant: "control" });
// a given user is enrolled in at most one of theseSplitting one population across two experiments means each gets a slice, not the whole. Use mutual exclusion when the experiments genuinely interact on the same surface — not as a default. Independent surfaces can run concurrently without it.
Rollout & measurement plan
- One universe per interacting surface. Group experiments that touch the same UI/flow; leave unrelated experiments in the
defaultuniverse so they keep full traffic. - Mind the sample split. Each experiment now draws from a fraction of the population — budget for a longer run or a larger effect to reach significance.
- The holdout measures the program. A universe holdout is a clean global control: compare holdout vs non-holdout to estimate the net effect of all the experimentation on that surface.
QA a flag before you ramp (and share a repro link)
Use the devtools overlay (Shift+Alt+S / ?se=1) and ?se_gate_… URL overrides to QA a feature, demo a variant, and reproduce a bug — all session-scoped.
Ship the winner only if guardrails hold
Pair a primary metric with guardrail metrics (latency, error rate) so a "winning" experiment still gets held if a core surface regresses.