Shipeasy
Flags & ExperimentsCase studies

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.

Production readyOn this page · 4 min readUpdated · June 19, 2026Works with · Server SDK · Dashboard

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 checkout

Bucketing 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 these
Mutual exclusion has a cost

Splitting 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 default universe 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.
Was this page helpful?
✎ Edit this page

On this page