rubyguides

Enumerable#cycle

#cycle repeats the elements of an array, or any enumerable, indefinitely or for a specific number of times. It is useful for round-robin scheduling, rotating through options, and generating repeating patterns without writing manual loops.

The method is easiest to read when the repetition itself is the point of the code. Instead of tracking indexes manually, you can ask Ruby to repeat the sequence for you and keep the loop focused on the work that happens for each item.

Basic usage

When called with a block, #cycle yields each element in order, then starts over from the beginning:

[:gold, :silver, :bronze].cycle { |medal| puts medal }
# gold
# silver
# bronze
# gold
# silver
# bronze
# ... forever until interrupted

This will run forever unless you explicitly break out of it, so it is best used when you know the loop will eventually stop on its own. In small scripts or demos that is often fine, but in production code you usually want a guard or a bounded form of the method.

Cycling a specific number of times

Pass an integer to #cycle(n) to repeat the collection exactly n times:

[:left, :right].cycle(3) { |direction| puts direction }
# left
# right
# left
# right
# left
# right

After completing the specified number of cycles, the loop ends and #cycle returns nil. That makes the bounded form easier to use in jobs that need a fixed number of passes, such as sending the same work across several workers.

Breaking out of a cycle

Use break to stop early, just like any other loop. This version is useful when you want the repeating pattern, but you also want an explicit exit condition:

result = []
[1, 2, 3].cycle do |n|
  result << n
  break if result.length == 5
end
result
# => [1, 2, 3, 1, 2]

The loop exits as soon as the result has five values, which keeps the example close to the way you would stop a real repeating process. That shape is clearer than leaving the stop rule implicit.

Without a block, returns an enumerator

Called without a block, #cycle returns an Enumerator so you can chain other Enumerable methods:

enum = [1, 2, 3].cycle
enum.take(5)
# => [1, 2, 3, 1, 2]

enum = [:a, :b, :c].cycle(2)
enum.to_a
# => [:a, :b, :c, :a, :b, :c]

That enumerator form is handy when you want to combine cycle with methods like take, map, or each_with_index. It gives you one place to define the repetition and another place to decide how much of it to consume.

Practical examples

Round-robin scheduling

Assign tasks to workers in rotation:

workers = [:alice, :bob, :carol]
tasks   = [:upload, :process, :deliver, :archive, :notify]

assignments = tasks.zip(workers.cycle)
assignments
# => [[:upload, :alice], [:process, :bob], [:deliver, :carol],
#      [:archive, :alice], [:notify, :bob]]

This style works well when the work should rotate through a pool of values without any manual indexing. The cycle call keeps the pattern obvious, while zip shows exactly how the assignments line up.

Rotating menus

Display items in a repeating carousel:

menu = ["Breakfast", "Lunch", "Dinner"]
menu.cycle.take(7)
# => ["Breakfast", "Lunch", "Dinner", "Breakfast", "Lunch", "Dinner", "Breakfast"]

That example is deliberately small so the repeating pattern is easy to see. In a real app the same shape could drive menu rotation, rotating banners, or any other short list that needs to loop in a predictable order.

Generating a repeating pattern

# Create 12 months organized by quarter
quarters = ["Q1", "Q2", "Q3", "Q4"]
quarters.cycle.take(12)
# => ["Q1", "Q2", "Q3", "Q4", "Q1", "Q2", "Q3", "Q4", "Q1", "Q2", "Q3", "Q4"]

That pattern is handy whenever you want a repeated label sequence without writing nested loops. The output stays easy to read because the source list is still visible in the code and the repeating shape is spelled out by the method name.

Using cycle with break

result = []
[10, 20, 30].cycle do |n|
  break if n == 30
  result << n
end
result
# => [10, 20]

Performance notes

  • cycle creates an Enumerator lazily. No array duplication occurs until you consume it.
  • Calling cycle with no argument on a large or infinite collection without break or take will consume all available memory eventually.
  • For a known number of iterations, always pass that count: cycle(n) instead of cycle { ... } with a manual counter. It avoids the overhead of checking a break condition on every iteration.
  • Chaining #cycle with #first(n) or #take(n) is more idiomatic and safer than relying on break inside the block.

Return value

  • collection.cycle { |x| ... } returns nil and runs until break.
  • collection.cycle(n) { |x| ... } returns nil and runs n times, or until break.
  • collection.cycle with no block returns an Enumerator.
  • collection.cycle(n) with no block returns an Enumerator.

See Also