rubyguides

Enumerable#group_by

The group_by method organizes collection elements into groups based on a block evaluation. It returns a hash where keys represent each unique group and values are arrays containing the elements belonging to that group.

Basic Usage

numbers = [1, 2, 3, 4, 5, 6]
grouped = numbers.group_by { |n| n.even? }
# => {false=>[1, 3, 5], true=>[2, 4, 6]}

The block receives each element in order and returns a value that becomes the grouping key. Every element that produces the same key ends up in the same bucket, so the structure of the output hash depends entirely on how many distinct keys the block generates. That makes the block the single point of control for how the grouping works.

Grouping by string length

words = ["apple", "cat", "banana", "dog", "cherry"]
by_length = words.group_by { |word| word.length }
# => {5=>["apple"], 3=>["cat", "dog"], 6=>["banana", "cherry"]}

Using string length as a grouping key is a common way to categorise text data. The result tells you which words share the same length, which can be useful for layout decisions, text analysis, or any situation where the visual footprint of a string matters more than its actual content.

Grouping strings by first letter

names = ["Alice", "Bob", "Anna", "Bill"]
by_letter = names.group_by { |name| name[0] }
# => {"A"=>["Alice", "Anna"], "B"=>["Bob", "Bill"]}

Grouping by the first character is a natural fit for alphabetical indexing, phonebook-style listings, or any UI that organises entries under letter headings. The block is short enough to stay readable while still capturing a meaningful grouping rule that would be tedious to express as a loop with manual hash insertion.

With nested data

products = [
  { name: "Widget", price: 10 },
  { name: "Gadget", price: 25 },
  { name: "Gizmo", price: 10 }
]

by_price = products.group_by { |p| p[:price] }
# => {10=>[{name: "Widget", price: 10}, {name: "Gizmo", price: 10}], 
#     25=>[{name: "Gadget", price: 25}]}

When the collection contains hashes or other structured objects, group_by lets you pick any field as the grouping key without changing the original data. The output keeps the full records intact, so you can still access all the original fields within each group after the grouping is done. This is especially handy for reports, dashboards, and any view that needs to show data organised by a shared attribute.

Categorizing test results

scores = [85, 92, 78, 90, 55, 88]
grades = scores.group_by { |s|
  case
  when s >= 90 then "A"
  when s >= 80 then "B"
  when s >= 70 then "C"
  when s >= 60 then "D"
  else "F"
  end
}
# => {"B"=>[85, 88], "C"=>[78], "A"=>[92, 90], "F"=>[55]}

This example shows how group_by can turn a flat list of scores into letter-grade buckets in a single expression. The case statement inside the block produces clear, human-readable keys, and the resulting hash is ready to display without further transformation. This pattern works well for any grading, rating, or classification task where the categories are defined by thresholds.

Return Value

group_by always returns a hash. Empty collections return an empty hash:

[].group_by { |x| x }
# => {}

grouping data by a shared trait

group_by is at its best when the block can describe a single trait that naturally splits the collection into buckets. That might be a number parity, a category name, or the first letter of a string. The method keeps the grouping rule close to the data, which makes the code easier to scan than a hand-built hash with manual push logic. When the grouping key is clear, the result is easy to use in later steps.

It also gives the caller a clean place to turn a flat list into a structure that is easier to display or summarize. Once the data is grouped, each bucket can be counted, printed, or filtered without repeating the grouping rule. That makes group_by a good bridge between raw input and later reporting code, especially when the category can be described in one short block.

The method is especially clear when the output groups will be used as separate mini collections. Instead of scanning the original list again and again, the code can ask for the groups once and then work from the hash that comes back. That keeps the grouping logic in a single place and helps the rest of the program stay focused on what should happen within each bucket.

See Also