The 10,000-slot space
Every group has an internal bucketing space of 10,000 slots, numbered 0 through 9999. That is the whole space; there is no larger number, no scaling factor. When you add a member to a group, you give it a share, a non-negative integer of slots it owns. Members can have:- Equal shares. Five members at 2,000 slots each = 10,000 total, full coverage.
- Uneven shares. One test at 5,000, four at 1,250 each = 10,000 total.
- Partial coverage. Two tests at 4,000 each = 8,000 owned, 2,000 unallocated.
The assignment step
On page load, before per-experiment bucketing runs, the group does this:groupSalt, the group’s immutable UUID, generated on creation and never changed.userId, the visitor’s stable id, from the__abtestly_uidcookie (see bucketing for how that is minted).mod 10000, coerces the hash to a slot number.
slot? If the slot falls inside a member’s
share range, the visitor is eligible for that one member only.
Every other member of the group is silently skipped for this visitor.
If the slot is in the unallocated range, the visitor sees none of
the group’s experiments.
Slot ranges, worked out
Take a group with three members: A owns 3,000, B owns 4,000, C owns 1,000. Total = 8,000 owned; 2,000 unallocated.
The order members are listed in the group affects which slot range
each one gets, but not the split proportions. Reordering members
re-dices every visitor because the ranges shift.
Determinism and stickiness
The hash is deterministic. Given the samegroupSalt and userId:
- The visitor gets the same slot on page one and page 500.
- The same visitor on their next visit still hashes to the same slot.
- Two different visitors get statistically independent slots
(assuming
userIds are unique, which they are, UUIDv7).
Membership change → generation bump
Every time you add, remove, or change the share of a member, the group’s generation counter increments. Generation is part of the assignment:murmur3(groupSalt + '/' + userId + '/' + generation). Bumping the generation re-dices every
visitor into potentially different slots.
Why re-dice? Because if you added a new member and the new member
took slots away from an existing member, sticky membership would
lock existing visitors into ranges that no longer belong to their
member. Re-dicing fixes that at the cost of “some visitors switch
which test they see”.
The generation shows up on every exposure event (as gn) so the
results page can tell you if group membership changed mid-experiment.
The activity log also records generation
bumps.
Why exclusion runs before per-experiment bucketing
If it ran after, a visitor could be bucketed into experiment A, then into experiment B, and only then the exclusion check would kick one of them out. The “kick” would happen after A’s or B’s own bucketing hash was computed, leaving one experiment’s results with a mysterious “some visitors were bucketed but not exposed” cell. Running exclusion first cleanly separates the two decisions:- Which member of the group is this visitor eligible for? (Group decides.)
- Does the visitor enter that member’s traffic allocation, and which variant? (Member decides.)
Salt and code
If you want to re-derive a visitor’s group assignment in a Node script for an audit, the salt is inside the group’s config blob. Ask support for the salt; the hash is standard MurmurHash3 x86 32-bit, seed 0. Reference implementation:What does not shift a visitor’s group slot
- Cache clear on their browser resets their
userId, so they hash to a new slot, same as it would in any deterministic bucketing. - Publishing a new site config does not change the group assignment unless the change touched group membership.
- Adding a new experiment outside the group has no effect at all.
Next
Long-term holdouts
Deliberately unallocate a percentage of traffic to measure
long-tail effects.
Managing membership
Add, retire, and reorder members. What generation bumps do to
running experiments.