Array#rotate
Array#rotate returns a new array with all elements shifted cyclically to the left until the element at the given offset becomes the first element. It is useful for cycling through elements in a circular fashion — the last element wraps around to the back.
Because the method returns a new copy, it is safe to use when you want to preview a different ordering without changing the original data. That makes it a simple building block for queues, carousels, and round-robin logic.
Basic Usage
fruits = ["apple", "banana", "cherry", "date"]
rotated = fruits.rotate
puts rotated # => ["banana", "cherry", "date", "apple"]
puts fruits # => ["apple", "banana", "cherry", "date"]
By default, rotate uses an offset of 1, shifting the first element to the end.
That default is often enough when you just want to advance the sequence by one step. It keeps the method easy to remember because the no-argument form behaves like the most common rotation. The offset parameter gives you control when a single shift is not enough — you can jump ahead several positions or even rotate backward by passing a negative number, which counts from the right side of the collection.
Using an Offset
Pass an integer to control which element becomes first:
days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
# Positive offset: count from the left
puts days.rotate(2) # => ["Wednesday", "Thursday", "Friday", "Monday", "Tuesday"]
# Negative offset: count from the right
puts days.rotate(-1) # => ["Friday", "Monday", "Tuesday", "Wednesday", "Thursday"]
# Offset of 0: no rotation
puts days.rotate(0) # => ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
rotate vs shift
Both rotate and shift involve the first element, but they behave differently:
rotatemoves the first element to the end, returning a new array — the original is unchangedshiftremoves the first element entirely, reducing the array’s length
Choosing between them depends on whether you need the original data to stay intact. When you are building a pipeline of transformations and want each step to produce a fresh result, rotate is safer because it never touches the source. When you are consuming items from a queue and genuinely want them removed after processing, shift is the natural fit.
queue = ["first", "second", "third"]
rotated = queue.rotate
puts rotated # => ["second", "third", "first"]
puts queue # => ["first", "second", "third"] — unchanged
shifted = queue.shift
puts shifted # => "first"
puts queue # => ["second", "third"] — element removed
In-Place Rotation with rotate!
The bang variant rotate! rotates the array in place, modifying the original. This is the version to use when you do not need to keep the old ordering and want to avoid allocating a new array for each rotation step.:
order = ["A", "B", "C", "D"]
order.rotate!
puts order # => ["B", "C", "D", "A"]
Carousel Cycling
rotate is ideal for cycling through items like a carousel — each call advances to the next item. The pattern below shows a simple display rotation where the list is never mutated, just shifted forward one position at a time so the caller always receives the next item in sequence.:
slides = ["intro", "features", "pricing", "contact"]
current = slides.rotate
puts "Showing: #{current.first}" # => "Showing: features"
current = current.rotate
puts "Showing: #{current.first}" # => "Showing: pricing"
current = current.rotate
puts "Showing: #{current.first}" # => "Showing: contact"
current = current.rotate
puts "Showing: #{current.first}" # => "Showing: intro"
The repeated calls make the pattern obvious: each rotated copy becomes the input to the next turn. That is a useful way to model repeated display or scheduling tasks where the same items need to move through a fixed order.
Round-Robin Scheduling
Distribute tasks or games in a round-robin fashion:
teams = ["Red", "Blue", "Green", "Yellow"]
# Generate round 1 pairings
round1 = teams.rotate
pairings = round1.each_slice(2).to_a
puts pairings # => [["Blue", "Green"], ["Yellow", "Red"]]
# Advance for round 2
round2 = round1.rotate
pairings = round2.each_slice(2).to_a
puts pairings # => [["Green", "Yellow"], ["Red", "Blue"]]
This approach keeps the pairing logic straightforward because the array itself carries the rotation state. You do not need to calculate indexes manually or mutate the original list to get the next round.
Rotating work schedules
Assign rotating shifts fairly across team members:
staff = ["Alice", "Bob", "Carol", "Diana"]
def assign_shift(staff, week)
rotated = staff.rotate(week - 1)
rotated[0]
end
puts "Week 1: #{assign_shift(staff, 1)}" # => "Week 1: Bob"
puts "Week 2: #{assign_shift(staff, 2)}" # => "Week 2: Carol"
puts "Week 3: #{assign_shift(staff, 3)}" # => "Week 3: Diana"
puts "Week 4: #{assign_shift(staff, 4)}" # => "Week 4: Alice"
The helper keeps the rotation rule in one place, which makes the schedule easier to adjust later. If the rotation amount changes, only the helper needs to know about it.
Return Value
rotatereturns a new copy with elements rotated; the original is unchangedrotate!returns the same object (self) after rotating in place
letters = ["a", "b", "c"]
new_arr = letters.rotate
puts new_arr.class # => Array
puts letters.class # => Array (unchanged)
same_arr = letters.rotate!
puts same_arr.class # => Array (same object)
puts same_arr.equal?(letters) # => true
The return values show the difference clearly: rotate gives you a new array, while rotate! keeps the existing object and changes its order. That split follows the usual Ruby convention for bang methods.
Performance Note
rotate has O(n) time complexity because it creates a new collection and copies all elements. The space complexity is also O(n) due to the new copy allocation.
rotate! has the same time complexity but avoids allocation since it modifies the list in place (O(n) time, O(1) extra space).
Use the bang version when the original data is disposable or when avoiding the extra allocation matters more than preserving the old order. If the caller still needs the original sequence, the non-bang form is the safer choice.
See Also
- /reference/array-methods/array-shift/ — remove and return the first element
- /reference/array-methods/array-pop/ — remove and return the last element
- /reference/enumerable/enumerable-cycle/ — loop over elements repeatedly