In Ruby, how do Kernel#rand, Random.rand and Random.new(seed) differ, and how do you make random results reproducible?
answer
- one default generator, many instances
- rand(1.5) is rand(1)
- Random#rand(0): ArgumentError
- same seed, same sequence
- Mersenne Twister: not for secrets
basics
~20 sKernel#rand and Random.rand draw from Ruby's shared default generator, while Random.new(seed) creates an independent one. The same seed replays the same sequence, and shuffle and sample accept random: for repeatable results. None of them is fit for secrets.
solid answer
~40 s`rand` with no argument returns a Float in `0.0...1.0`, `rand(6)` an Integer from 0 to 5, and `rand(1..6)` a member of the range. `Kernel#rand` converts its limit with `to_i` and `abs`, so `rand(1.5)` is always `0` and `rand(0)` returns a Float, while `Random.rand(1.5)` returns a Float below 1.5 and `Random.rand(0)` or `Random.new.rand(0)` raise `ArgumentError`. `srand(seed)` reseeds the shared default generator for the whole process and returns the previous seed; `Random.new(42)` gives an independent generator whose sequence repeats for the same seed, and `shuffle(random:)` and `sample(random:)` take one. The generator is a Mersenne Twister, so tokens and passwords belong to `SecureRandom`. `Random::DEFAULT` was removed in Ruby 3.2.
code
ruby · 12 linesrand(1..6) # => an Integer from 1 to 6
rand(1.5) # => 0 (limit.to_i is 1)
Random.rand(1.5) # => a Float below 1.5
a = Random.new(42)
b = Random.new(42)
[a.rand(100), a.rand(100)] == [b.rand(100), b.rand(100)] # => true
deck = (1..10).to_a
deck.shuffle(random: Random.new(7)) == deck.shuffle(random: Random.new(7)) # => true
Random.new.rand(0) # ArgumentError: invalid argument - 0go deeper
Know rand, rand(n) and rand(range), and that Random.new(seed) gives the same numbers every run for the same seed.
Explain the shared default generator versus instances, the Kernel versus Random argument rules, and how random: makes shuffle and sample repeatable.
Inject seeded generators so tests are repeatable without srand, log seeds for replay, and keep Random away from anything security-sensitive.
Set the team rule: randomness is a dependency passed in explicitly, reproducible in tests and cryptographic wherever an attacker could benefit.
## One default generator, many instances Ruby's **`Random`** class wraps a pseudo-random number generator (PRNG): a deterministic sequence that only looks random. There are two ways to use it: - The **default generator**, shared by the whole process, behind `Kernel#rand`, `Kernel#srand`, `Random.rand`, `Random.srand` and the default `random:` of `Array#shuffle` and `Array#sample`. - **Independent instances** created with `Random.new` or `Random.new(seed)`, each with its own state. Drawing from one does not advance the others. `Random::DEFAULT`, once the handle to the shared generator, was deprecated in Ruby 3.0 and removed in 3.2; in Ruby 4.0 use the class methods instead. ## Arguments and return values | Call | Returns | |---|---| | `rand`, `Random.rand` | Float in `0.0...1.0` | | `rand(6)`, `Random.rand(6)` | Integer from 0 to 5 | | `rand(1..6)`, `Random.new.rand(1..6)` | Integer from 1 to 6 (range member) | | `rand(1.5)` | always `0` - Kernel converts the limit with `to_i` | | `Random.rand(1.5)`, `Random.new.rand(1.5)` | Float in `0.0...1.5` | | `rand(0)` | Float in `0.0...1.0` | | `Random.rand(0)`, `Random.new.rand(0)` | raises `ArgumentError` (invalid argument - 0) | | `rand(-100)` | Integer below 100 - Kernel uses the absolute value | The Kernel version is the forgiving one: it truncates the limit and takes its absolute value. `Random#rand` and `Random.rand` are strict and treat a Float limit as a Float range. ## Making results reproducible 1. **Seed an instance** and pass it around: `rng = Random.new(42)`. Two generators built with the same seed produce the same sequence, and `rng.seed` returns the seed for logging. 2. **Pass the generator to library calls**: `list.shuffle(random: rng)` and `list.sample(3, random: rng)` both accept `random:`. 3. **Inject it** into the code under test (a keyword argument with a `Random.new` default) so production stays unpredictable while tests are repeatable. 4. **Avoid `srand` in library code.** `srand(42)` reseeds the shared generator for every caller in the process and returns the previous seed; a test that calls it silently changes what unrelated code draws next. Random objects can also be marshaled, so a generator's position can be saved and restored. ## Not for secrets The documentation is explicit: `Random` is a **modified Mersenne Twister** with a period of 2 to the 19937th minus one, and it is **not for cryptographic use**. Its future output can be predicted from enough observed output, and a seeded generator is predictable by design. Session tokens, password-reset codes and API keys come from `SecureRandom`. ## Common mistakes - **Treating `rand(1.5)` like `Random.rand(1.5)`.** The Kernel version truncates the limit, so a Float limit below 2 always yields `0`. - **Guarding against `rand(0)` differently per call style.** Kernel returns a Float; `Random` raises. Code that switches from `rand(n)` to an injected `rng.rand(n)` can start raising when `n` is zero. - **Seeding the process in a helper.** A `srand` call hidden in a factory or fixture makes every later draw in the suite depend on test order. - **Building a fresh seeded generator per draw.** `Random.new(42).rand(100)` in a loop returns the same number every time, because each generator restarts the sequence. - **Porting old code that names `Random::DEFAULT`.** On Ruby 4.0 it raises `NameError`; replace it with `Random` class methods or an explicit instance. ## What interviewers look for - Knowing that `rand` and `Random.rand` share one generator while `Random.new` does not. - The Kernel-versus-`Random` differences for Float and zero limits. - Seeding an **instance** for reproducible tests rather than reseeding the process. - Naming the security boundary without being asked.
- In a Ruby test suite, why is calling srand(1234) inside one test risky?`srand` reseeds the process-wide default generator that `rand`, `Random.rand`, `shuffle` and `sample` use by default. After that call every later draw in the process is predictable and shared, so unrelated tests become order-dependent on each other's draws. Create a `Random.new(1234)` and pass it to the code under test or to `shuffle(random:)` instead.
- In Ruby, how do you log enough to replay a random run later?Create the generator with an explicit seed, for example `seed = Random.new_seed` and `rng = Random.new(seed)`, log the seed, and pass `rng` everywhere randomness is drawn. Replaying with `Random.new(logged_seed)` reproduces the same sequence as long as the same calls happen in the same order. `rng.seed` returns the seed if it was not kept separately.
saying these in an interview costs you the question
- srand(42) only affects the code that called it.
- rand(1.5) returns a Float up to 1.5, just like Random.rand(1.5).
- Random.new(seed) is fine for session tokens if the seed is kept secret.
- Random::DEFAULT is how you reach the shared generator in Ruby 4.0.
- Kernel#rand and Random#rand treat a limit of 0 the same way.