Array#combination
arr.combination(n) { |c| block } Array#combination generates all possible unordered arrangements of array elements of a given size. This method is essential for solving combinatorial problems, generating test cases, and exploring mathematical relationships in your data.
Syntax
array.combination(n) { |c| block }
Returns an Enumerator when called without a block, allowing you to chain other Enumerable methods or convert to an array.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| n | Integer | Required | The size of each combination to generate |
Examples
Basic usage
# Generate all combinations of 2 elements
[1, 2, 3].combination(2).to_a
# => [[1, 2], [1, 3], [2, 3]]
This basic example returns an array of all two-element combinations from a three-element set. The result is always ordered by the original array’s element order — you never get [2, 1] because combinations ignore internal ordering. That consistency makes the output easy to reason about when you need to iterate over distinct groups.
With a block
# Using a block with combination
[1, 2, 3].combination(2) do |c|
puts "Combination: #{c.join(', ')}"
end
# Combination: 1, 2
# Combination: 1, 3
# Combination: 2, 3
The block form gives you control over each combination as it is generated. You can print, filter, or accumulate results one group at a time, which is especially useful when the total number of combinations is large and you want to avoid building a full array in memory. The code inside the block sees each group as an array that you can destructure or iterate over.
Practical examples
# Generate all possible teams of 2 from a list of players
players = ["Alice", "Bob", "Charlie", "Diana"]
teams = players.combination(2).to_a
# => [["Alice", "Bob"], ["Alice", "Charlie"], ["Alice", "Diana"],
# ["Bob", "Charlie"], ["Bob", "Diana"], ["Charlie", "Diana"]]
This practical example demonstrates the most common real-world use of combination: building teams or groups from a list where every member is distinct. The method produces unique sets without worrying about order, which is exactly right for team formation. When the players list grows, the same single call handles all possible pairings with no additional loops.
Common Patterns
Password cracking simulation
characters = ['a', 'b', 'c']
characters.combination(2).each do |combo|
puts combo.join
end
# ab
# ac
# bc
The password cracking simulation shows how combination generates non-overlapping, unordered groups — each pair of characters appears exactly once regardless of position. This is different from permutation, which would produce both “ab” and “ba” as separate entries. Knowing which method produces which output helps you pick the right tool for problems where order matters or does not.
Lottery number generation
numbers = (1..49).to_a
winning_numbers = numbers.combination(6).first
The lottery example shows how combination works with large input ranges — the method generates the combinations lazily through its enumerator, so calling first on the result only materializes one set of six numbers rather than computing all possible picks. For larger draws, this keeps memory usage constant regardless of how large the pool of candidates is.
Error Cases
# Negative size raises ArgumentError
[1, 2, 3].combination(-1)
# => ArgumentError: negative combination size (-1)
# Size greater than array length returns empty enumerator
[1, 2].combination(5).to_a
# => []
# Size equal to zero returns single empty array
[1, 2, 3].combination(0).to_a
# => [[]]
thinking in combinations
combination is a good fit when the order of the selected items does not matter, but the group itself does. That shows up in team building, testing, menu choices, and search problems where every unique group should be explored once. The method keeps the logic compact because Ruby handles the counting and grouping for you, leaving the block or enumerator to focus on what happens to each combination after it is produced.
Because the number of results can grow quickly, it is smart to think about the size first. A small n can still produce a lot of output when the input array is large, so the code that consumes the combinations should be ready for that. Keeping the search space visible in the code makes the tradeoff easier to understand.
keeping the result set under control
It helps to treat combination as a deliberate search, not a casual helper. Once the input grows, the result count can climb fast, which means the code that receives the output should be ready for a larger list than it first expects. In practice, that often means filtering the source array first or choosing a smaller group size before you ask for every possible grouping. Clear boundaries keep the method useful without turning it into an accidental explosion of work.