control — your current behaviour — and one or more variants proposing a change.
Both ship in the same build, deploy at the same time, and run simultaneously. Different users
get different ones, and nobody sees more than one.
Assignment is a hash, not a lottery
When you callgetVariant(), no network request happens. The SDK computes:
getVariant() is synchronous and returns in
under a millisecond, so it’s safe in a render path.
It’s reproducible. Given a user id and an experiment key, you can compute which variant
they’ll get without asking Trevo anything — useful when debugging a report of “this user says
the button is missing”.
The hash is FNV-1a over UTF-16 code units, and it’s a frozen contract shared by every Trevo
SDK. The browser, Node, and any future client all bucket identically, which is what lets a
user get the same variant on the web and from your backend. See the
bucketing spec if you need the normative details.
Identity decides everything
Assignment is a function of identity, so the question “which variant does this user get?” reduces to “who does Trevo think this user is?”- Before
identify(), that’s an anonymous id stored in browser local storage - After
identify(userId), it’s your user id
identify() can move a user from
one variant to another, because the input to the hash changed. If they were mid-session and
looking at the treatment, they may see the control after logging in.
The fix is to identify early — before rendering anything under test. See
Variant mismatch.
Anonymous ids are also per browser. The same person on a laptop and a phone is two
participants until they sign in, and nothing can retroactively merge their pre-login history
on the second device.
Traffic splits
Splits are whole percentages that must sum to 100. The SDK warns in the console if a misconfigured experiment doesn’t add up, and assigns as best it can rather than failing. 50/50 is the default and usually right — equal groups reach significance fastest. Uneven splits make sense when you’re being cautious about a risky change, at the cost of needing more total traffic to reach the same confidence.More than two variants
Nothing stops you testing three or four arms, anddefineExperiment() gives you a typed
union so an unhandled arm is a compile error rather than a silent fallthrough:
Unknown keys return control
getVariant() returns 'control' for an experiment key it doesn’t recognise — one that
doesn’t exist yet, is finished, or is paused. It logs a warning the first time, but never
throws.
That’s deliberate: it means you can merge experiment code before the experiment exists in
Trevo, and a stopped experiment degrades to your current behaviour rather than breaking the
page.
Experiments end
When a winner is called, a cleanup PR promotes the winning code and deletes the losing branch along with thegetVariant() call. The flag is temporary by design — you end with plain code,
not an accumulating pile of dead conditionals.