The obvious way to assign users to variants is to decide once and write it down. Hash-based assignment does the opposite: it computes the answer from the user's identifier every time it is needed. Hash the identifier together with the experiment id, take the result modulo 100, and map that number onto the allocation ranges. The same inputs always produce the same output, so no state is stored and none can go stale.
That statelessness is worth more than it first appears. A web front end, a mobile client, a recommendation service and a nightly analytics job can each compute a user's variant independently, with no shared database and no network call, and they will agree. There is no assignment table to keep consistent, no read on the critical path, and no cache invalidation problem. For a platform running hundreds of concurrent experiments this is the difference between a viable design and one that adds a database lookup to every request.
The hash function needs specific properties and cryptographic strength is not among them. What matters is uniformity — buckets equally likely — and good avalanche behaviour, meaning a single-bit change in the input scrambles the output. Sequential user ids are the test case: a weak hash can leave neighbouring ids in neighbouring buckets, which correlates assignment with signup date and therefore with tenure. MurmurHash3 is the usual choice; MD5 works and is slower for no benefit here.
The input has three parts and each earns its place. The unit identifier must be stable — a user id where available, a first-party cookie otherwise. The experiment id makes assignments independent across concurrent tests, which is the property most commonly lost by omitting it. And a salt or layer identifier lets a platform re-randomise deliberately, so that a long-running series of experiments does not keep exposing the same users to the same arm.
The one operational hazard is that changing any part of the input reassigns everybody. Rotating the salt, renaming an experiment, or altering how the identifier is derived all move users between arms mid-flight, which is exactly the failure that produces a sample ratio mismatch. These inputs should be treated as immutable for the life of an experiment, and the platform should refuse to change them on a running test rather than trusting anyone to remember.
One expression, and the properties each component supplies. Everything about this technique is contained in what goes into the hash.
A platform team compares two hash functions for assignment quality on 500,000 sequential integer user ids, then simulates the effect of rotating the salt while an experiment is running.
- Hash A: MurmurHash3, bucket uniformity
- χ² = 104.2 on 99 df, p = 0.34
- Hash B: simple modulo of the id
- χ² = 0.0, perfectly uniform
- Hash B: correlation of bucket with signup date
- 0.0004 — but adjacent ids adjacent
- Hash B: users signing up in one hour
- spread over 3 consecutive buckets
- Salt rotation mid-experiment
- 49.6% of users change arm
- Resulting SRM after rotation
- χ² = 1,840, p < 10⁻¹⁰⁰
The simple modulo produces a perfectly even split and clusters users who signed up together. Rotating the salt reassigns half the population.
Hash B is the instructive failure because its headline check passes perfectly. A modulo of a sequential id gives exactly uniform bucket counts — better than MurmurHash's honest randomness — while placing everyone who signed up in the same hour into a handful of adjacent buckets. Any experiment whose allocation boundaries fall between those buckets is then comparing cohorts rather than random samples, and the SRM check will not notice because the totals are even. That is why avalanche behaviour matters more than uniformity here. The salt rotation row is the operational lesson: it is a legitimate and useful capability between experiments, and inside a running one it silently rebuilds both arms from different people. The platform should make it structurally impossible rather than documented as inadvisable.
- דA cryptographic hash is the safest choice for assignment.”
- It is slower for no relevant benefit. Assignment needs uniformity and good avalanche behaviour, not resistance to adversarial attack — nobody is trying to forge a bucket. MurmurHash3 and similar non-cryptographic hashes are designed for exactly this profile and are the standard choice in experimentation platforms.
- דAn even split proves the hash is assigning well.”
- A simple modulo of a sequential id gives a perfectly even split and clusters users who signed up at the same time into adjacent buckets. Uniform totals are compatible with strongly structured assignment. The property that matters is that similar inputs land in unrelated buckets, which even bucket counts do not demonstrate.
- דStoring assignments in a table is more reliable than recomputing them.”
- It adds a lookup to every request, a consistency problem between services, and state that can drift. Hashing gives the same guarantee with none of that, which is why it is standard. Storage is genuinely needed only when assignment must survive a change to the hash inputs, which is a rare and deliberate situation.