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
cyclecreates an Enumerator lazily. No array duplication occurs until you consume it.- Calling
cyclewith no argument on a large or infinite collection withoutbreakortakewill consume all available memory eventually. - For a known number of iterations, always pass that count:
cycle(n)instead ofcycle { ... }with a manual counter. It avoids the overhead of checking a break condition on every iteration. - Chaining
#cyclewith#first(n)or#take(n)is more idiomatic and safer than relying onbreakinside the block.
Return value
collection.cycle { |x| ... }returnsniland runs untilbreak.collection.cycle(n) { |x| ... }returnsniland runsntimes, or untilbreak.collection.cyclewith no block returns an Enumerator.collection.cycle(n)with no block returns an Enumerator.
See Also
- Enumerable#take - limit iterations by taking a set number of elements
- Enumerable#reduce - aggregate values across iterations
- Enumerable#uniq - remove duplicates from an enumerable