rubyguides

Array#sample

What does sample do?

Array#sample returns one or more random elements from an array. Without an argument, it returns a single random element. With an argument n, it returns an array of n unique random elements.

The method does not modify the original array — it returns a selection while leaving the caller intact.

This is useful for picking random items from a collection: a winner from a contest, a random record from a dataset, a sample of test data, or a feature flag variant.

Basic Usage

Returning a single random element

fruits = ["apple", "banana", "cherry", "date"]

fruits.sample  # => "cherry"  (random each time)
fruits.sample  # => "apple"
fruits.sample  # => "date"

Each call to sample without arguments returns one randomly chosen element from the array, and the original collection stays unchanged no matter how many times you call it. The method handles edge cases gracefully — calling it on an empty array returns nil rather than raising an error:

[].sample  # => nil

A single call to sample on an empty array returns nil rather than raising an error, which means you can safely call it on any array without a guard clause. To get more than one random element, pass an integer argument — the returned elements are guaranteed unique within the result:

Returning multiple random elements

Pass an integer n to get n random elements. The elements are unique — no element appears twice in the result:

fruits = ["apple", "banana", "cherry", "date", "elderberry"]

fruits.sample(2)  # => ["banana", "elderberry"]
fruits.sample(3)  # => ["apple", "cherry", "date"]

Calling sample with a count argument gives you that many unique random elements from the array. The result is always an array, even when you request just one element. If n is 0, an empty array is returned — a safe default that avoids branching in dynamic code:

fruits.sample(0)  # => []

Passing zero is safe and returns an empty array, which simplifies code that dynamically computes the sample count. When n exceeds the number of elements, Ruby does not raise an error — it returns all elements in random order instead:

If n is greater than the number of elements in the array, all elements are returned in random order — no error is raised:

fruits = ["apple", "banana"]

fruits.sample(5)  # => ["banana", "apple"] - all elements, shuffled

When n exceeds the size of the array, Ruby returns all elements in random order rather than raising an error — a forgiving design that means you never need to check bounds. The summary table below clarifies the difference between calling sample with and without an argument:

How many to pick: sample vs sample(n)

CallReturns
array.sampleA single random element (or nil)
array.sample(n)An array of up to n unique random elements
deck = [:ace, :king, :queen, :jack, :ten]

deck.sample       # => :queen
deck.sample(3)    # => [:king, :ace, :ten]

When n exceeds the array length, Ruby returns all elements in random order rather than raising an error, which means you can safely pass any value of n without bounds checking. By default, sample selects without replacement — each element appears at most once in the result:

sampling without replacement

By default, sample selects without replacement — once an element is chosen, it cannot be chosen again in the same call. This is the correct behaviour for most use cases like selecting winners or shuffling a hand of cards.

colors = ["red", "blue", "green", "yellow"]

colors.sample(3)  # => ["green", "red", "yellow"] - 3 unique choices

The no-replacement default is usually what you want for selecting winners, dealing cards, or picking a random subset. To sample with replacement — allowing the same element to appear multiple times — you must call sample repeatedly in a loop, such as with times.map:

# With replacement: roll a die 10 times
die = [1, 2, 3, 4, 5, 6]

rolls = 10.times.map { die.sample }
rolls  # => [3, 1, 4, 4, 2, 6, 3, 5, 1, 2]

custom random number generator

When you need reproducible results — for tests, simulations, or debugging — you can control the source of randomness by passing a random: keyword argument with a seeded Random object. This guarantees the same sequence of selections every time:

rng = Random.new(42)

[1, 2, 3, 4, 5].sample(3, random: rng)  # => [5, 1, 3]
[1, 2, 3, 4, 5].sample(3, random: rng)  # => [5, 1, 3] - same result
[1, 2, 3, 4, 5].sample(3)                # => [2, 4, 1] - different

The random: keyword also accepts any object that responds to #rand, such as Random::DEFAULT. This means you can pass custom random generators for specialised distributions, or use a seeded Random instance to produce identical output across multiple test runs:

# Deterministic: same seed always gives same sequence
r = Random.new(123)
[10, 20, 30, 40, 50].sample(2, random: r)  # => [20, 10]

A seeded generator produces the same sequence every time you run the program, which is critical for tests that need deterministic output from random operations. In contrast, shuffle randomises the entire array order while sample picks a subset — two different operations that are easy to confuse:

Calling sample repeatedly in a loop gives you with-replacement behaviour — the same element can appear multiple times across calls. This is the pattern to use for dice rolls, coin flips, or any scenario where each draw is independent. A related but different operation is shuffle, which reorders the entire collection rather than picking a subset:

A seeded Random object with random: gives you deterministic output for tests and simulations. Two methods that are easy to confuse are sample and shuffle — they both involve randomness but serve different purposes:

sample vs shuffle

Both sample and shuffle return random elements from an array, but they differ in what they return:

MethodReturnsSize of result
sampleRandom subset of elements1 (default) or n you specify
shuffleAll elements, reorderedAlways the full array
deck = [:ace, :king, :queen, :jack, :ten]

deck.shuffle      # => [:queen, :ten, :ace, :jack, :king] - all 5, shuffled
deck.sample       # => :king                               (1 random)
deck.sample(2)    # => [:ace, :queen]                      - 2 random

Think of it this way: shuffle is for reordering a whole collection, while sample is for picking a random subset.

sample vs rand with index

You can generate a random index with rand and use it to access an array element, but this is more error-prone than sample:

names = ["Alice", "Bob", "Carol"]

# Manual random index — awkward and easy to get wrong
names[rand(names.length)]  # => "Carol"

# Equivalent with sample — cleaner
names.sample                # => "Bob"

Using rand(array.length) breaks down for picking multiple elements because you have to track which indices you’ve already chosen. sample(n) handles this correctly and efficiently.

Practical Examples

Passing a seeded Random object gives you reproducible randomness, which is essential for tests and simulations where you need the same “random” output on every run. The practical examples below show how sample fits into real application code, starting with the most common use case:

Picking a random winner

contestants = ["Alice", "Bob", "Carol", "Dave", "Eve"]

winner = contestants.sample
puts "The winner is #{winner}!"
# => The winner is Carol!

Picking a single winner with sample is the simplest use case and works for any contest or lottery where you need one random selection. When you need more than one winner without allowing duplicates, pass the count as an argument:

Selecting multiple winners

contestants = ["Alice", "Bob", "Carol", "Dave", "Eve", "Frank"]

first = contestants.sample(2)
puts "Winners: #{first.join(' and ')}"
# => Winners: Dave and Carol

Selecting multiple winners with sample(2) ensures each person can only win once, which is the correct behaviour for most contest scenarios. The same no-replacement principle applies when generating random passwords from a word list — you want each word to appear at most once:

Random password from a word list

words = ["apple", "banana", "cherry", "dragon", "elephant", "forest", "galaxy", "harbor"]

password = 4.times.map { words.sample }.join("-")
password  # => "forest-banana-galaxy-dragon"

Using sample with a word list produces passwords that are easier to remember than random characters while still being hard to guess when enough words are combined. The method is also useful for implementing feature flags with randomised rollout percentages:

Random feature flags

features = {
  new_checkout: 0.1,   # 10% of users
  dark_mode:    0.25,  # 25% of users
  beta_search:  0.05   # 5% of users
}

enabled = features.select { |_, ratio| rand <= ratio }.keys
enabled  # => [:dark_mode] - random each call

Feature flags with random ratios let you gradually roll out changes to a percentage of users. Another common pattern is picking random records from a dataset for testing, where you want a small representative sample without loading or sorting the full collection:

Sampling test data

users = (1..1000).map { |id| { id: id, name: "User #{id}" } }

# Get 5 random test users
test_sample = users.sample(5)
test_sample.each { |u| puts "Testing user #{u[:id]}" }
# => Testing user 342
# => Testing user 7
# => Testing user 891
# => Testing user 56
# => Testing user 623

Sampling test data lets you verify behaviour across a representative subset without running every record through your test suite. The same technique works for simulations and games \u2014 for example, drawing a random hand of cards from a full deck:

Randomising a hand of cards

deck = [:ace, :king, :queen, :jack, :ten].product([:hearts, :diamonds, :clubs, :spades]).flatten(1)

hand = deck.sample(5)
hand  # => [[:queen, :clubs], [:ace, :hearts], [:ten, :diamonds], [:jack, :spades], [:king, :clubs]]

This hand-drawing technique works for any card game simulation where you need a random subset from a deck. The approach scales well, but it helps to understand the performance characteristics when working with very large arrays:

Performance Considerations

sample is implemented efficiently. For a single-element sample, Ruby uses a constant-time random index lookup — O(1). For multi-element sampling, Ruby uses a variant of reservoir sampling that avoids fully shuffling the array, making it more efficient than shuffle when you only need a subset.

Performance degrades gracefully as n approaches the array size. If you need all elements randomised, shuffle is equivalent and may be clearer in intent.

require "benchmark"

array = (1..10_000).to_a

Benchmark.measure { array.sample }.real        # => ~0.00001s  (single element)
Benchmark.measure { array.sample(100) }.real   # => ~0.0001s
Benchmark.measure { array.sample(5000) }.real   # => ~0.002s
Benchmark.measure { array.shuffle }.real        # => ~0.003s

For very large arrays where you only need a small subset, sample(n) is typically faster than shuffle.first(n).

For very large arrays where you only need a small subset, sample(n) is typically faster than shuffle.first(n) because it uses reservoir sampling rather than a full shuffle. The edge cases below cover the boundary behaviours you may encounter:

Edge Cases

For very large arrays where you only need a small subset, sample(n) is typically faster than shuffle.first(n) because it uses reservoir sampling rather than a full shuffle. The edge cases below cover the boundary behaviours you may encounter:

This hand-drawing technique works for any card game simulation where you need a random subset from a deck. Before relying on sample in production code, understand how it behaves at the boundaries — the edge cases below cover the scenarios you are most likely to encounter:

Edge Cases

Empty array

[].sample    # => nil
[].sample(0)  # => []
[].sample(1)  # => [] - not nil — n is clamped to array length

An empty array with n=1 returns an empty array rather than nil, because n is clamped to the array length and zero is as large as the result can be. When n is explicitly 0, the result is predictably an empty array regardless of the array contents:

n is 0

[1, 2, 3].sample(0)  # => []

Passing 0 as the argument produces an empty array, which is useful when your code dynamically computes the sample size and zero is a valid result. When n exceeds the number of available elements, Ruby does not raise an error — it simply returns all elements in random order:

n exceeds array length

[1, 2].sample(5)  # => [2, 1] - all elements, in random order

When you ask for more elements than the array contains, Ruby returns a shuffled copy of the whole array rather than raising an error or repeating elements. For a single-element array, sample always returns that one element regardless of the n argument:

Single-element array

[42].sample     # => 42
[42].sample(1)  # => [42]
[42].sample(2)  # => [42]

A single-element array is a degenerate case where every call to sample returns the same value. When nil is present in the array, it is treated as a valid element and can be randomly selected just like any other value:

nil in the array

[1, nil, 3, nil, 5].sample  # => nil - nil is a valid element
[1, nil, 3, nil, 5].sample(2)  # => [nil, 1] - nil can be selected

When nil appears in the array, it is treated as a normal element and can be randomly selected. If you need to exclude nil values from sampling, call compact on the array first. For related array methods that filter or transform collections, see the references below:

See Also