Enumerable#chunk

Returns: Enumerator · Added in v1.9 · Updated March 16, 2026 · Enumerable
ruby enumerable grouping chunking

The chunk method groups consecutive elements that return the same value from the block. It’s ideal for processing runs of identical values, state changes, or categorized data without loading everything into memory first.

How It Works

chunk passes each element to the block and groups consecutive elements with the same return value:

enumerable.chunk { |element| block_value }
# Returns an Enumerator of [block_value, elements_array] pairs

Basic Usage

Group consecutive even and odd numbers:

[3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5].chunk { |n| n.even? }.to_a
# => [[false, [3, 1]], [true, [4]], [false, [1, 5, 9]], [true, [2, 6]], [false, [5, 3, 5]]]

Practical Examples

Processing Log Files

Group consecutive log entries by level:

log_levels = [:info, :info, :warn, :error, :error, :info, :debug, :debug]

log_levels.chunk { |level| level }.each do |level, entries|
  puts "#{level}: #{entries.count} entries"
end
# Output:
# info: 2 entries
# warn: 1 entries
# error: 2 entries
# info: 1 entries
# debug: 2 entries

Grouping Consecutive Letters

Find runs of consecutive characters in a string:

"aaabbbccdaa".chars.chunk(&:itself).map { |char, arr| [char, arr.length] }
# => [["a", 3], ["b", 3], ["c", 2], ["d", 1], ["a", 2]]

Processing CSV Data

Group rows by a category column:

rows = [
  { category: "fruit", name: "apple" },
  { category: "fruit", name: "banana" },
  { category: "vegetable", name: "carrot" },
  { category: "fruit", name: "date" },
  { category: "vegetable", name: "eggplant" }
]

rows.chunk { |row| row[:category] }.each do |category, items|
  puts "#{category}: #{items.map { |i| i[:name] }.join(', ')}"
end
# Output:
# fruit: apple, banana
# vegetable: carrot
# fruit: date
# vegetable: eggplant

Finding State Changes

Track when a status changes in a sequence:

statuses = [:idle, :idle, :running, :running, :running, :complete, :idle, :idle]

statuses.chunk_while { |before, after| before == after }.each do |run|
  puts "Status: #{run.first}, Duration: #{run.length}"
end
# Output:
# Status: idle, Duration: 2
# Status: running, Duration: 3
# Status: complete, Duration: 1
# Status: idle, Duration: 2

Grouping Test Results

Process test results in batches:

results = [:pass, :pass, :fail, :fail, :pass, :pass, :pass]

results.chunk { |r| r == :pass ? :passed : :failed }.each do |status, tests|
  puts "#{status}: #{tests.count} tests"
end
# Output:
# passed: 2 tests
# failed: 2 tests
# passed: 3 tests

Using :underscore to Skip Elements

The special :underscore symbol tells chunk to skip that element entirely:

[1, 2, 3, :skip, 4, 5, 6, :skip, 7].chunk { |x| x == :skip ? :_skip : x.even? }.to_a
# => [[false, [1]], [true, [2]], [false, [3]], [true, [4, 6]], [false, [7]]]

This is useful for filtering out certain values while still grouping the rest.

MethodWhat It Does
chunkGroups consecutive elements by block return value
group_byGroups all elements regardless of order/consecutive
slice_whenSplits when block returns true between elements
chunk_whileLike slice_when but creates Enumerator
# chunk - only groups consecutive elements
[1, 2, 1, 2].chunk { |x| x.odd? }.to_a
# => [[true, [1]], [false, [2]], [true, [1]], [false, [2]]]

# group_by - groups all matching elements together
[1, 2, 1, 2].group_by { |x| x.odd? }
# => {true => [1, 1], false => [2, 2]}

Return Value

Returns an Enumerator. Use to_a to get an array, or iterate directly:

[1, 2, 2, 3, 3, 3].chunk { |n| n }
# => #<Enumerator: [1, 2, 2, 3, 3, 3]:chunk>

[1, 2, 2, 3, 3, 3].chunk { |n| n }.each { |k, v| puts "#{k}: #{v}" }
# 1: [1]
# 2: [2, 2]
# 3: [3, 3, 3]

Edge Cases

Empty collections return an empty enumerator:

[].chunk { |x| x }.to_a
# => []

Single element:

[42].chunk { |x| x }.to_a
# => [[42, [42]]]

All same values:

[1, 1, 1, 1].chunk { |x| x }.to_a
# => [[1, [1, 1, 1, 1]]]

All different values:

[1, 2, 3, 4].chunk { |x| x }.to_a
# => [[1, [1]], [2, [2]], [3, [3]], [4, [4]]]

See Also