Skip to Content
Course content

60: group_by and partition

Click on the "Edit" button in the top corner of the screen to edit your slide content.

At some point in every project, you're going to find yourself staring at a big list of objects and thinking, "I need to organize these into buckets." Maybe it's grouping orders by their shipping status or separating users into those who have verified their emails and those who haven't. Most developers start by doing this manually, and while it works, it usually results in a lot of boilerplate that obscures what you're actually trying to achieve.

The manual bucket-sorting headache

Imagine we have a list of Task objects, and each has a priority: :high, :medium, or :low. If you were approaching this naively, you'd probably initialize an empty hash and loop through your tasks, manually checking if a key exists before pushing the task into an array. It looks something like this:

tasks = [
  { name: "Fix bug", priority: :high },
  { name: "Update docs", priority: :low },
  { name: "Email client", priority: :high },
  { name: "Refactor CSS", priority: :medium }
]

grouped = {}
tasks.each do |task|
  priority = task[:priority]
  grouped[priority] ||= []
  grouped[priority] << task
end

I've seen this pattern a thousand times. It's not "wrong"—it's logically sound—but it's noisy. You're spending three lines of code just managing the state of the hash (the ||= [] trick) rather than describing the relationship between the data and the group. When I review PRs, this is the first thing I flag, not because it's slow, but because it's mentally taxing to read. You have to simulate the loop in your head to realize, "Oh, they're just grouping by priority."

Letting Ruby handle the hash

This is exactly why group_by exists. It collapses that entire loop into a single, declarative line. Instead of telling Ruby how to build the hash, you tell it what the key should be.

grouped = tasks.group_by { |task| task[:priority] }

That's it. Ruby handles the hash initialization and the array pushing behind the scenes. The result is identical to the manual approach, but the intent is immediate. If you're grouping by a simple attribute, you can even use the shorthand symbol syntax: tasks.group_by { _1[:priority] }. I prefer this because it keeps the focus on the data structure rather than the plumbing.

When you only have two piles

Now, group_by is great for an unknown or large number of categories. But what happens when you only have two options? Say you want to split your tasks into "Urgent" (high priority) and "Not Urgent" (everything else).

A common mistake I see is developers using select twice. They'll run one select to get the urgent tasks, and then another reject (or another select with a negated condition) to get the rest. While that feels clean, you're actually iterating over your entire collection twice. If you have ten thousand items, you've just done twice the work for no reason.

You could use group_by here, but that returns a hash. If you only need two arrays, a hash is overkill and forces you to access your data via keys like grouped[true] and grouped[false], which feels clunky.

The efficiency of a single split

This is where partition comes in. Think of partition as a specialized version of group_by that specifically returns a two-element array: the first element contains everything that returned true for the block, and the second contains everything that returned false.

urgent, others = tasks.partition { |task| task[:priority] == :high }

I love this method because it combines the efficiency of a single pass with the elegance of parallel assignment. You get two distinct arrays immediately. It's much more intentional than group_by when the logic is binary. If you find yourself writing a group_by where the block returns a boolean, you should probably be using partition instead.




📋 Practical Task

Categorizing an Inventory of Out-of-Stock Items

You are managing a warehouse inventory. You have an array of product hashes, and you need to perform two specific organizational tasks for a morning report.

Given the following data:

products = [
  { name: "Mechanical Keyboard", category: :peripherals, stock: 0 },
  { name: "Gaming Mouse", category: :peripherals, stock: 5 },
  { name: "27-inch Monitor", category: :displays, stock: 0 },
  { name: "USB-C Cable", category: :accessories, stock: 12 },
  { name: "HDMI Adapter", category: :accessories, stock: 0 },
  { name: "4K Monitor", category: :displays, stock: 2 }
]

Write a script that does the following:

  1. Use group_by to create a hash where the keys are the product categories and the values are arrays of products belonging to that category.
  2. Use partition to split the original products list into two separate arrays: out_of_stock (where stock is 0) and in_stock (where stock is greater than 0).
  3. Print the out_of_stock array to verify your results.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.