Deterministic assignment
Deterministic assignment
A visitor is assigned to a variant by a pure function of (visitor id, experiment key) — no
rand(), no time, no database state:
point = hash(visitorId ':' experimentKey) mod totalWeight
variant = the arm whose cumulative-weight window contains `point`
The hash is SHA-256; its top 60 bits are taken as an integer (well within PHP's 64-bit signed
range, so the modulo is exact), which spreads visitor ids uniformly across the weight space.
Variants are walked in a stable order — control first, then sort_order, then id — so the
cumulative windows are identical on every call.
This gives three properties at once:
- Sticky — the same visitor always lands on the same variant, so a returning visitor sees a consistent price and never flaps between refreshes.
- Reproducible & testable — a fixed set of visitor ids maps to a fixed, assertable split. There is no seed to store or lose: re-running the assignment over the same ids yields the same buckets, so a test can assert both "this id always maps here" and "10,000 ids split ≈ to the weights within tolerance".
- Weighted — an arm with twice the weight receives ≈ twice the traffic.
Worked example
With a control weight of 1 and a challenger weight of 3 (totalWeight = 4), a visitor whose
hash mod 4 is 0 lands on the control and 1–3 on the challenger — a 25% / 75% split. Feeding a
fixed batch of 10,000 synthetic ids through the assigner reproduces that split to within a couple
of percent every time.
The visitor id
The visitorId is an anonymous, random cookie — see attribution for its
privacy properties. It is the only per-visitor input to assignment; everything else is the
experiment's static configuration.
The assigner is App\Billing\Experiments\VariantAssigner. Its bucket() method is public so a
test — or an operator debugging why a visitor saw a particular price — can reproduce the exact
bucket independent of the variant set.