Skip to content
Logo

Distributions

By default, a range-bound value — T.number.whereby({ min, max }), T.date.whereby({ min, max }), T.date.past, T.date.future, T.string.whereby({ length }), T.array(...).whereby({ length }) — draws uniformly: every value in the range is equally likely. A scalar min/max is inclusive; { value, exclusive: true } excludes that end. A distribution shapes the draw inside that interval, so generated data can cluster the way real-world data usually does.

T.number.whereby({
  min: 0,
  max: 1000,
  distribution: { kind: "normal", mean: 500, spread: 100 },
});

Explore

Pick a variant, set the range, and drag its parameters until the shape matches your data. The bars are real draws from T.number.whereby, the line is the exact density they follow, and the snippet underneath is the call that produces what you see. A configuration the library rejects shows the library's own error.

02004006008001,000
5,000 draws exact density
T.number.whereby({
  min: 0,
  max: 1000,
  distribution: { kind: "normal", mean: 500, spread: 100 },
});

The variants

uniform

{ kind: 'uniform' } — the default. Every value in the range equally likely.

02004006008001,000

No parameters — every value is equally likely.

2,000 draws exact density

normal

{ kind: 'normal', mean?, spread? } — a bell curve truncated to the range. mean defaults to the range's center; spread (standard deviation) defaults to a sixth of the span, which puts the range's bounds at roughly ±3σ before truncation.

02004006008001,000
2,000 draws exact density

skew

{ kind: 'skew', exponent } — a power curve. exponent > 1 biases toward min, exponent < 1 biases toward max, exponent === 1 is uniform.

02004006008001,000
2,000 draws exact density

triangular

{ kind: 'triangular', mode? } — linear ramps peaking at mode (defaults to the range's center). Simpler than a normal curve when you just want "values cluster around here, taper off toward the edges" without the bell-curve shape.

02004006008001,000
2,000 draws exact density

logarithmic

{ kind: 'logarithmic' } — log-uniform: density proportional to 1/x, so values spread evenly across orders of magnitude and cluster toward min. Good for things like request latencies or file sizes, where "10 vs 100" and "1000 vs 10000" should feel equally likely. Requires a strictly positive range — the range's min must be greater than zero. Toggle the log axis to see the same draws come out flat.

12,0004,0006,0008,00010,000

No parameters — the range must start above zero.

2,000 draws exact density

multi

{ kind: 'multi', components: [{ weight, distribution }, ...] } — a weighted blend of component distributions, each drawn over the same range. Two normals at different means, blended, produce the two separate peaks of a bimodal distribution. Weights are relative and don't need to sum to one. A component with weight 0 is dropped; every component zero throws.

{
  kind: 'multi',
  components: [
    { weight: 3, distribution: { kind: 'normal', mean: 20, spread: 5 } },
    { weight: 1, distribution: { kind: 'normal', mean: 80, spread: 5 } },
  ],
}
// clusters mostly around 20, with a smaller cluster around 80
020406080100
component 1
component 2
2,000 draws exact density

custom

{ kind: 'custom', shape } — the escape hatch. shape is an inverse CDF: (u: number) => number, mapping a uniform draw in [0, 1) to a position in [0, 1) within the range. The result is clamped to [0, 1], so it always lands within bounds no matter what shape returns.

What every variant guarantees

Every distribution, including custom, produces a value within [min, max] by construction — truncated via inverse CDF rather than rejected or clamped mid-range, so no distribution can silently loop or bias the output by discarding out-of-range draws.