There is a common pattern in code where a programmer picks a random element from a list by generating a large random number and reducing it modulo the number of choices: `choice = items[rng() % n]`. The blog post shows why this fails to give each item an equal chance. If the random number generator yields values uniformly from 0 to 9 (ten possibilities) and you want to choose one of three objects, the mapping is: values {0,3,6,9} → first object, {1,4,7} → second, {2,5,8} → third. Thus the first object is selected 4 out of 10 times (40 %), while the other two each appear only 3 out of 10 times (30 %). This loss of uniformity is called modulo bias.

The correct way to preserve uniformity is to use a function that returns an integer uniformly in a desired range, such as `random_between(l, h)`. For three choices you would call `random_between(0,2)` and map the result directly. A more expressive API is `random_choice(options)` that accepts relative integer weights; for example `[(A,4),(B,3),(C,3)]` yields the same 40/30/30 distribution when that is truly intended, but lets the programmer specify the desired odds explicitly. The post notes that most standard libraries (e.g., Python’s `random.randrange`) already implement this approach, and that having `random_between` available makes building `random_choice` straightforward.

Diagram showing modulo bias in random selection with ten values mapped to three outcomes
Illustration of how modulo operation causes uneven distribution when selecting random itemsersc.io

The image visualizes the modulo bias example described in the article, where ten random values map unevenly to three choices, causing selection probabilities of 40% and 30%.

At Antithesis, the author’s team reviewed their fuzzing code and replaced every ad‑hoc `random_u64() % n` call with a `weighted_choice` helper that drives blob‑size distribution (e.g., 88 % Small, 4 % Medium, 4 % Large, 4 % XLarge). This change eliminated a hidden source of non‑deterministic bias in their test coverage and led to a moratorium on new `random_u64()` usages until a better abstraction is in place. The episode illustrates how a seemingly innocuous convenience can distort randomized experiments and why a higher‑level, intention‑revealing API is preferable.