Skip to content

Random

Random is a pseudo-random number generator exposed through Effect’s context. Instead of reaching for Math.random() directly, you read randomness from the current Random service. That single indirection is what makes random programs reproducible: in a test or a local simulation you can seed the generator and get the exact same sequence every time.

Random is a Context.Reference, which means it is always available — there is no layer to provide. A default Math.random-backed implementation is installed for you, and you can override it (per-effect with withSeed, or globally with a custom generator) wherever you need determinism.

import { Effect, Random } from "effect"
const program = Effect.gen(function*() {
const randomFloat = yield* Random.next // 0 <= x < 1
const randomInt = yield* Random.nextInt // any safe integer
const diceRoll = yield* Random.nextIntBetween(1, 6)
return { randomFloat, randomInt, diceRoll }
})

All of the generators below return an Effect with no error channel (Effect.Effect<A>) — drawing a random value cannot fail.

The core generators cover floats, integers, booleans, and bounded ranges. Read them with yield* inside Effect.gen:

import { Effect, Random } from "effect"
const program = Effect.gen(function*() {
// A double in [0, 1) — the analogue of Math.random()
const f = yield* Random.next
// A coin flip
const flip = yield* Random.nextBoolean
// A safe integer anywhere in the full safe-integer range
const big = yield* Random.nextInt
// A float in a custom range [0, 10)
const temp = yield* Random.nextBetween(0, 10)
// An integer in a range — inclusive by default, so this is a d6
const dice = yield* Random.nextIntBetween(1, 6)
// Exclude the upper bound: a uniform index into a 6-element array
const index = yield* Random.nextIntBetween(0, 6, { halfOpen: true })
return { f, flip, big, temp, dice, index }
})

shuffle randomly reorders any iterable and returns a new Array (the input is not mutated). It uses a Fisher–Yates shuffle driven by the active Random service:

import { Effect, Random } from "effect"
const program = Effect.gen(function*() {
const deck = yield* Random.shuffle([1, 2, 3, 4, 5])
console.log(deck) // => e.g. [3, 1, 5, 2, 4]
// Works on any iterable, e.g. a Set
const picks = yield* Random.shuffle(new Set(["a", "b", "c"]))
console.log(picks) // => e.g. ["b", "c", "a"]
})

Because randomness flows through the service, you can replace it with a seeded generator using withSeed. The same seed always produces the same sequence, so a flaky “it depends on randomness” test becomes a deterministic one:

import { Effect, Random } from "effect"
const program = Effect.gen(function*() {
const a = yield* Random.next
const b = yield* Random.next
return [a, b] as const
})
// Same seed => identical output, run after run
const seeded1 = program.pipe(Random.withSeed("test-seed"))
const seeded2 = program.pipe(Random.withSeed("test-seed"))
// Both promises resolve to the same pair of numbers
Effect.runPromise(seeded1)
Effect.runPromise(seeded2)

This is exactly the mechanism that underpins reproducible, property-based-style tests: seed the program, exercise it, and if it fails you can replay the same random sequence to debug it. See the testing guide for how to structure deterministic tests around services like this.

Generates a random double in [0, 1) (inclusive of 0, exclusive of 1) — the direct analogue of Math.random().

import { Effect, Random } from "effect"
Effect.gen(function*() {
const x = yield* Random.next
// => 0.5488135039273248 (0 <= x < 1)
})

Generates a random boolean — true roughly half the time. (Internally it draws a double and returns whether it is greater than 0.5.)

import { Effect, Random } from "effect"
Effect.gen(function*() {
const b = yield* Random.nextBoolean
// => true | false
})

Generates a random safe integer across the entire range from Number.MIN_SAFE_INTEGER to Number.MAX_SAFE_INTEGER (inclusive). Use nextIntBetween when you want a bounded integer.

import { Effect, Random } from "effect"
Effect.gen(function*() {
const n = yield* Random.nextInt
// => -3920481039402011 (some safe integer)
})

(min: number, max: number) => Effect<number> — generates a random float in [min, max) (inclusive of min, exclusive of max).

import { Effect, Random } from "effect"
Effect.gen(function*() {
const x = yield* Random.nextBetween(10, 20)
// => 14.73... (10 <= x < 20)
})

(min: number, max: number, options?: { halfOpen?: boolean }) => Effect<number> — generates a random integer in a rounded range. The lower bound is rounded up (Math.ceil), the upper bound rounded down (Math.floor). Inclusive of both bounds by default; pass { halfOpen: true } to exclude the upper bound.

import { Effect, Random } from "effect"
Effect.gen(function*() {
// Inclusive: result is one of 1, 2, 3, 4, 5, 6
const dice = yield* Random.nextIntBetween(1, 6)
// => 4
// Half-open: result is one of 0, 1, 2, 3, 4, 5 (good for array indices)
const idx = yield* Random.nextIntBetween(0, 6, { halfOpen: true })
// => 5
})

<A>(elements: Iterable<A>) => Effect<Array<A>> — returns a new array with the elements of any iterable in random order, leaving the input untouched.

import { Effect, Random } from "effect"
Effect.gen(function*() {
const shuffled = yield* Random.shuffle(["a", "b", "c", "d"])
// => ["c", "a", "d", "b"]
})
// data-last (for .pipe)
withSeed(seed: string | number): <A, E, R>(self: Effect<A, E, R>) => Effect<A, E, R>
// data-first
withSeed<A, E, R>(self: Effect<A, E, R>, seed: string | number): Effect<A, E, R>

Runs an effect with a deterministic generator seeded by the given string or number. Internally it provides a seeded ISAAC-based generator in place of the default Random service for the duration of self. The same seed always yields the same sequence. It is dual, so it works both in .pipe(...) and as a direct call.

import { Effect, Random } from "effect"
const draw = Random.next
// data-last, via pipe
const a = draw.pipe(Random.withSeed(42))
// data-first, direct call
const b = Random.withSeed(draw, 42)
// a and b resolve to the same number
Effect.runPromise(a) // => 0.123...
Effect.runPromise(b) // => 0.123... (identical)
const Random: Context.Reference<{
nextIntUnsafe(): number
nextDoubleUnsafe(): number
}>

The service itself. It is a Context.Reference holding a minimal generator interface: nextDoubleUnsafe() returns a double in [0, 1) and nextIntUnsafe() returns a safe integer. Every function above is derived from these two methods. The Unsafe suffix means they are plain, synchronous, non-Effect functions — prefer the wrapped generators (Random.next, Random.nextInt, …) in application code.

You normally interact with it through withSeed, but you can also provide a fully custom generator — for example a fixed sequence in a test, or a secure source in production:

import { Effect, Random } from "effect"
// A custom generator that always returns the same values — handy for asserting
// on code paths that branch on randomness.
const fixed = {
nextDoubleUnsafe: () => 0.5,
nextIntUnsafe: () => 7
}
const program = Effect.gen(function*() {
const d = yield* Random.next // => 0.5
const n = yield* Random.nextInt // => 7
const r = yield* Random.nextIntBetween(1, 100) // derived from 0.5 => 51
return { d, n, r }
})
// Override the reference for this effect
const deterministic = program.pipe(
Effect.provideService(Random, fixed)
)
Effect.runPromise(deterministic) // => { d: 0.5, n: 7, r: 51 }