Enumerable#each_cons

Returns: Enumerator · Added in v1.8.7 · Updated March 16, 2026 · Enumerable
ruby enumerable iteration sliding-window

The each_cons method groups consecutive elements together and iterates over each group. Unlike each_slice which takes elements in order from the start, each_cons always uses consecutive elements with no gaps.

How It Works

each_cons(n) takes n consecutive elements at a time:

collection.each_cons(n) { |group| block }

Each iteration gives you a slice of n consecutive elements. The last group may have fewer than n elements if the collection length isn’t evenly divisible.

Basic Usage

Process pairs of consecutive elements:

[1, 2, 3, 4, 5].each_cons(2) { |pair| p pair }
# => [1, 2]
# => [2, 3]
# => [3, 4]
# => [4, 5]

Triple groups:

[1, 2, 3, 4, 5].each_cons(3) { |group| p group }
# => [1, 2, 3]
# => [2, 3, 4]
# => [3, 4, 5]

Sliding Window Algorithms

Moving Averages

Calculate a 3-element moving average:

values = [10, 20, 30, 40, 50]

averages = values.each_cons(3).map { |window| window.sum.to_f / window.size }
# => [20.0, 30.0, 40.0]

Rate of Change

Find the difference between consecutive elements:

prices = [100, 105, 103, 110]

changes = prices.each_cons(2).map { |a, b| b - a }
# => [5, -2, 7]

Finding Peaks

Detect when a value is greater than both neighbors:

readings = [1, 3, 2, 5, 4, 6]

peaks = readings.each_cons(3).select { |prev, curr, next_| curr > prev && curr > next_ }
# => [[1, 3, 2], [2, 5, 4]]
# Peak values: 3, 5

Pairwise Comparisons

Compare every consecutive pair:

words = ["cat", "dog", "bird", "fish"]

words.each_cons(2) { |a, b| puts "#{a} -> #{b}" }
# cat -> dog
# dog -> bird
# bird -> fish

Sequential Analysis

Finding Consecutive Sequences

numbers = [1, 2, 3, 5, 6, 7, 9]

sequences = numbers.each_cons(2).chunk_while { |a, b| b == a + 1 }.to_a
# => [[1, 2, 3], [5, 6, 7], [9]]

Validating Ordered Data

Check if a list is sorted:

def sorted?(arr)
  arr.each_cons(2).all? { |a, b| a <= b }
end

sorted?([1, 2, 3, 4])  # => true
sorted?([1, 3, 2, 4])  # => false

Running Differences

values = [100, 102, 105, 110]

diffs = values.each_cons(2).map { |a, b| [a, b, b - a] }
# => [[100, 102, 2], [102, 105, 3], [105, 110, 5]]

Window Size Considerations

Small Windows

Window size of 2 (pairs):

[1, 2, 3].each_cons(2).to_a
# => [[1, 2], [2, 3]]

Large Windows

(1..10).each_cons(4).to_a
# => [[1, 2, 3, 4], [2, 3, 4, 5], [3, 4, 5, 6], [4, 5, 6, 7], [5, 6, 7, 8], [6, 7, 8, 9], [7, 8, 9, 10]]

Window Larger Than Collection

[1, 2, 3].each_cons(5).to_a
# => [] (no groups of 5)

Practical Examples

Comparing Adjacent List Items

tasks = ["wake up", "brush teeth", "eat breakfast", "go to work"]

tasks.each_cons(2) { |prev, curr| puts "After #{prev}: do #{curr}" }
# After wake up: do brush teeth
# After brush teeth: do eat breakfast
# After eat breakfast: do go to work

Polynomial Coefficients

coefficients = [1, -3, 3, -1]  # (x - 1)^3

coefficients.each_cons(2).map { |a, b| b / a.to_f }
# => [-3.0, -1.0, -0.3333333333333333]

Text Analysis

Find consecutive capital letters:

text = "TheQuickBrownFox"

caps = text.chars.each_cons(2).select { |a, b| a =~ /[A-Z]/ && b =~ /[A-Z]/ }
# => [["Q", "B"], ["B", "F"]]
MethodBehavior
each_consConsecutive elements, overlapping groups
each_sliceNon-overlapping groups from start
each_chunkGroups based on block return value
combinationAll possible n-element combinations
permutationAll possible orderings
[1, 2, 3].each_cons(2).to_a   # => [[1, 2], [2, 3]] (consecutive)
[1, 2, 3].each_slice(2).to_a  # => [[1, 2], [3]] (non-overlapping)
[1, 2, 3].combination(2).to_a # => [[1, 2], [1, 3], [2, 3]] (all combos)

Return Value

Returns an Enumerator if no block is given:

enum = [1, 2, 3, 4].each_cons(2)
# => #<Enumerator: [1, 2, 3, 4]:each_cons(2)>

enum.to_a
# => [[1, 2], [2, 3], [3, 4]]

Edge Cases

Empty collection:

[].each_cons(2).to_a
# => []

Single element:

[1].each_cons(2).to_a
# => []

Nil values are included:

[1, nil, 3].each_cons(2).to_a
# => [[1, nil], [nil, 3]]

See Also