APM Academy

Generating Random Numbers in Ruby: rand, Random & SecureRandom

Generating Random Numbers in Ruby: rand, Random & SecureRandom

To generate a random number in Ruby, call rand: rand(10) returns an integer from 0–9, rand(1..10) an integer from 1–10, and rand alone a float between 0.0 and 1.0. For security-sensitive values like tokens and passwords, use SecureRandomrand is predictable.

Computers cannot produce truly random numbers by computation alone. A deterministic machine can only produce pseudorandom numbers: a stream that looks random but comes from an algorithm fed with a starting value, the seed. That detail matters. It decides which generator you should reach for, and it makes seeded generators reproducible in tests.

In this article, we’ll work through Ruby’s randomness toolkit: rand, the Random class, SecureRandom, Random::Formatter, and seeding with srand. Every example runs on Ruby 3.4.

Generating Random Numbers with Kernel#rand

Kernel#rand is available everywhere in Ruby. Called without arguments, it returns a float greater than or equal to 0.0 and less than 1.0.

Ruby
rand
# => 0.7308136972953823

Pass an integer to get a random integer from 0 up to (but not including) that number. Each call below returns a number between 0 and 7:

Ruby
rand(8)
# => 5

For a random number within a particular range, pass a Range to rand.

An inclusive range (..) covers both limits. This call can return any integer from 1 to 10, including 10:

Ruby
rand(1..10)
# => 6

An exclusive range (...) leaves out the upper limit. This call never returns 10:

Ruby
rand(1...10)
# => 9

Float ranges return floats, and negative ranges work as you’d expect:

Ruby
rand(1.5..3.0)
# => 1.7494305393711571
 
rand(-5..-1)
# => -5

A single negative argument can surprise you:

Ruby
rand(-100)
# => 94
 
rand(-0.5)
# => 0.7692627344737486

For an argument n, rand returns numbers from 0 up to (but not including) n.to_i.abs. So rand(-100) behaves like rand(100), and rand(-0.5) behaves like rand(0), which behaves like rand(): a float between 0.0 and 1.0.

Generating Random Numbers with Random

The Random class powers Kernel#rand under the hood. Its class method Random.rand accepts the same arguments, with stricter input handling: a negative or zero argument raises an ArgumentError instead of quietly taking the absolute value.

Ruby
Random.rand(1...10)
# => 5
 
Random.rand(-5)
# ArgumentError (invalid argument - -5)

The more interesting use is Random.new, which gives you your own generator instance with its own internal state:

Ruby
generator = Random.new(42)
5.times.map { generator.rand(100) }
# => [51, 92, 14, 71, 60]

Two instances created with the same seed produce identical sequences. That gives you reproducibility without touching global state: a seeded instance passed into one class can’t be thrown off by another part of the codebase calling rand. Call Random.new without arguments to get an unpredictable seed, readable afterwards via generator.seed.

Generating Secure Values with SecureRandom

Everything covered so far is predictable by design. Someone who knows (or guesses) the seed can reproduce every value, which rules rand out for anything an attacker might want to guess. For those cases, Ruby ships SecureRandom, which reads entropy from your operating system instead of a seedable algorithm.

require "securerandom" and pick the format you need. hex returns a hexadecimal string; the argument is the number of random bytes, so hex(4) yields 8 hex characters:

Ruby
require "securerandom"
 
SecureRandom.hex
# => "6eca3a2088f80c73c3b4a0ef9a01f379"
 
SecureRandom.hex(4)
# => "0a9876d6"

uuid returns a random (version 4) UUID. Ruby 3.3 added uuid_v7, which encodes a timestamp in the leading bits, so values sort by creation time. That makes v7 a better fit for database keys:

Ruby
SecureRandom.uuid
# => "06c3b563-337b-418e-b12c-bf1e1a6b917a"
 
SecureRandom.uuid_v7
# => "01a013f4-0661-78b8-9c2d-71dd4ba972e9"

alphanumeric returns a string of letters and digits, 16 characters by default. Since Ruby 3.3, a chars: keyword restricts the alphabet, which is handy for codes that avoid ambiguous characters:

Ruby
SecureRandom.alphanumeric
# => "MRMaZWYtc7ZaUFQk"
 
SecureRandom.alphanumeric(8)
# => "On0OD3Cx"
 
SecureRandom.alphanumeric(10, chars: ("a".."f").to_a)
# => "cbdaeffcbd"

random_number mirrors rand: no argument returns a float between 0.0 and 1.0, an integer argument an integer from 0 up to that number, and it accepts ranges too:

Ruby
SecureRandom.random_number
# => 0.39749494698989263
 
SecureRandom.random_number(100)
# => 75
 
SecureRandom.random_number(1..6)
# => 4

Use SecureRandom for password reset tokens, API keys, session identifiers, temporary passwords, and anything else where predictability is a vulnerability. It cannot be seeded, so it has no place in deterministic tests. That’s a feature.

Formatting Random Values with Random::Formatter

The formatting methods on SecureRandom (hex, uuid, alphanumeric, and friends) live in a module called Random::Formatter. Requiring "random/formatter" mixes it into Random itself, so any Random instance, including a seeded one, gains the same methods:

Ruby
require "random/formatter"
 
generator = Random.new(42)
 
generator.hex
# => "66dce15fb33deacb5c0362f30e95f52e"
 
Random.new(42).uuid
# => "66dce15f-b33d-4acb-9c03-62f30e95f52e"
 
Random.new(42).alphanumeric
# => "SyWMkJRvgNMiHV6O"

Given the same seed, these outputs are identical on every run. That’s useful when a test needs stable “random-looking” fixtures: token-shaped strings or UUIDs you can assert against. It’s the same reason these values must never leave your test suite. A seeded formatter produces predictable tokens, so production code should keep calling SecureRandom.

Generating Reproducible Sequences with Kernel#srand

Kernel#srand sets the seed for the global generator behind Kernel#rand. Same seed, same sequence, every run:

Ruby
srand(2024)
 
rand
# => 0.5880145188953979
 
5.times.map { rand(10) }
# => [0, 0, 4, 7, 9]

Run it again with the same seed and you get the same values back:

Ruby
srand(2024)
 
rand
# => 0.5880145188953979
 
5.times.map { rand(10) }
# => [0, 0, 4, 7, 9]

This is where deterministic tests come from. A spec that fails only occasionally often depends on randomness somewhere: a sampled collection, a generated fixture, a probabilistic branch. Seeding pins that randomness down. RSpec applies the same idea to test ordering: it prints the seed of each run, and rspec --seed 1234 replays the exact order that failed. When your own code uses rand, an srand(1234) in a spec (or a seeded Random instance injected into the class under test) turns a flaky failure into a reproducible one.

srand also returns the previous seed, so you can capture and restore it. But srand only affects Kernel#rand; SecureRandom ignores it completely.

Randomness bugs rarely surface locally — a reused seed after a fork or a colliding “unique” value tends to appear as a rare, confusing production error. If you monitor with AppSignal for Ruby, error grouping is usually how that pattern first becomes visible.

Do You Need a Random Number Gem?

Rarely. The standard library covers almost every case:

You needReach forSeedable?Source
Quick numbers, sampling, jitterrand / Kernel#randYes, via srandGlobal Mersenne Twister PRNG
Reproducible, isolated sequencesRandom.new(seed)Yes, per instancePer-instance Mersenne Twister
Tokens, passwords, UUIDsSecureRandomNo, by designOS entropy (CSPRNG)
Token-shaped test fixturesRandom::Formatter on RandomYes, per instancePer-instance Mersenne Twister

Two cases fall outside the standard library. The first is statistical distributions. rand(150..200) treats every value as equally likely, but real-world data like heights, test scores, or measurement errors clusters around an average. The rubystats gem samples from proper distributions:

Shell
gem install rubystats
Ruby
require "rubystats"
 
adult_male_height = Rubystats::NormalDistribution.new(178, 10)
5.times.map { adult_male_height.rng.round(1) }
# => [177.5, 156.2, 174.7, 186.2, 171.5]

NormalDistribution.new(178, 10) takes a mean of 178 cm and a standard deviation of 10 cm, so most samples land near the mean and extremes stay rare. The gem still installs and runs fine on Ruby 3.4.

The second is realistic fake data. When a test needs names, emails, or addresses rather than raw numbers, Faker generates them, and it can be seeded for deterministic output.

For everything else — plain integers, floats, tokens, and UUIDs — stick with the standard library.

Random Roundup

Ruby gives you three generators with clear jobs. rand covers everyday randomness: integers, floats, and ranges. Random.new gives you seeded, isolated instances for reproducible sequences. SecureRandom draws on OS entropy for tokens, passwords, and UUIDs, including time-sortable v7 UUIDs on Ruby 3.3+. Random::Formatter bridges the two worlds with deterministic token-shaped values for tests, and srand turns flaky, randomness-dependent specs into reproducible ones.

If you’d like to see how your Ruby app behaves in production, randomness and all, the AppSignal for Ruby documentation shows how to set up monitoring in a few minutes.

If you have any comments or questions on what we covered, please reach out to us @AppSignal. You can also send us your requests for topics you want covered.

Frequently asked questions

How do I generate a random number in a range in Ruby?
Pass a Range to rand. In Ruby 3.4, rand(1..10) returns an integer from 1 to 10 inclusive, rand(1...10) excludes the upper limit, and a float range like rand(1.5..3.0) returns a float within that range.
What is the difference between rand and SecureRandom in Ruby?
rand uses a fast, seedable pseudorandom generator, so its output is reproducible and predictable. SecureRandom reads entropy from the operating system and cannot be seeded. Use rand for games and sampling, and SecureRandom for tokens, passwords, and UUIDs.
How do I seed Ruby’s Random for deterministic tests?
Call srand with a fixed integer, like srand(1234), to seed the global generator behind Kernel#rand; the same seed always reproduces the same sequence. For better isolation, create your own instance with Random.new(1234) and inject it into the code under test.

Published , Updated

Wondering what you can do next?

  • Share this article on social media

Become our next author!

Find out more
$appsignal install

AppSignal monitors your apps

AppSignal provides insights for Ruby, Rails, Elixir, Phoenix, Node.js, Express and many other frameworks and libraries. We are located in beautiful Amsterdam. We love stroopwafels. If you do too, let us know. We might send you some!

Discover AppSignal