Rotate Array Problem Explained — 3 Approaches, Real Trade-offs
- Always reduce k with k % n before rotating — skipping this turns an O(n) algorithm into O(n²) or causes silent redundant full cycles.
- The reversal trick works because two inversions cancel out: reversing the whole array then reversing each segment independently restores internal order in a new overall position.
- The destination-index formula (i + k) % n loop when gcd(n, k) > 1 and the cycle does not return to the starting index — Fix: use a count variable to track total elements moved, and terminate is reusable across circular buffers, Caesar ciphers, and round-robin schedulers — memorise it, not just for rotations.
- Brute force: O(n*k) time, O(1) space. Rotate one step at a time. Only for tiny k.
- Extra array: O(n) time, O(n) space. Clean and readable. Use when memory is free.
- Reversal trick: O(n) time, O(1) space. Three in-place reversals. Optimal.
- Cyclic replacement: O(n) time, O(1) space. Move elements directly to final positions in cycles.
- Circular buffers, round-robin schedulers, and job queues all use array rotation.
- Always normalize k = k % n first. Without it, large k causes redundant full cycles.
- Off-by-one in reversal boundaries. reverse(0, steps) instead of reverse(0, steps-1) corrupts the result.
Rotation produces wrong result when k >= array length.
Print k before and after normalization: System.out.println("k=" + k + " n=" + n + " k%n=" + (k % n))Test with k = n: should return original array unchangedReversal trick produces result with two adjacent elements in wrong order.
Print the intermediate state after each reversal stepVerify: after step 1 (full reverse), the array is fully reversedArrayIndexOutOfBoundsException on rotation.
Print: System.out.println("k=" + k + " n=" + n + " k-1=" + (k-1))If k-1 >= n, the normalization is missingExtra array approach returns original array unchanged.
Print buffer after population: System.out.println(Arrays.toString(buffer))If buffer is correct but original is unchanged, the copy-back is missingProduction Incident
Production Debug GuideSymptom-first investigation path for rotation bugs.
Array rotation is a fundamental operation in circular buffers, round-robin schedulers, job queues, and image processing pipelines. Every OS kernel that rotates a task queue by one slot per tick, and every CDN that rotates a server pool for load balancing, relies on efficient array rotation.
The problem: shift every element k positions to the right, wrapping the tail to the front. The naive approach rotates one step at a time: O(n*k). The optimal approach uses three in-place reversals: O(n) time, O(1) space. The difference matters at scale — rotating a 10000 steps takes 5 billion operations with brute force vs 200,000 with the reversal trick.
The common misconception is that the reversal trick is 'just a trick' with no real-world relevance. In production, circular buffers use the (i + k) % n destination formula for O(1) element access without physically rotating the array. The reversal trick is the physical rotation equivalent when you need the array contents in the rotated0,000-element array by 50, order.
How to Rotate an Array — Three Approaches
Rotating an array right by k steps means the last k elements move to the front. Example: [1,2,3,4,5], k=2 → [4,5,1,2,3].
Approach 1 — Extra array (O(n) time, O(n) space): 1. Create a new array result of same size. 2. For each i: result[(i+k) % n] = arr[i]. 3. Copy result back to arr.
Approach 2 — Reverse trick (O(n) time, O(1) space — optimal): 1. Normalize: k = k % n. 2. Reverse the entire array. 3. Reverse the first k elements. 4. Reverse the remaining n-k elements.
Worked example: [1,2,3,4,5], k=2. Step 1: Reverse all → [5,4,3,2,1]. Step 2: Reverse first 2 → [4,5,3,2,1]. Step 3: Reverse last 3 → [4,5,1,2,3]. Result: [4,5,1,2,3]. Correct.
Why it works: reversing the whole array puts each element in the mirror of its final position. Reversing the two halves individually corrects both halves simultaneously.
package io.thecodeforge.algo; import java.util.Arrays; public class ArrayRotationOverview { /** * Reversal trick: O(n) time, O(1) space. * Three in-place reversals achieve full rotation. */ public static void rotateRight(int[] arr, int k) { int n = arr.length; if (n == 0) return; k = k % n; if (k == 0) return; reverse(arr, 0, n - 1); // Step 1: reverse entire array reverse(arr, 0, k - 1); // Step 2: reverse first k elements reverse(arr, k, n - 1); // Step 3: reverse remaining elements } private static void reverse(int[] arr, int from, int to) { while (from < to) { int temp = arr[from]; arr[from] = arr[to]; arr[to] = temp; from++; to--; } } public static void main(String[] args) { int[] arr = {1, 2, 3, 4, 5, 6, 7}; System.out.println("Before: " + Arrays.toString(arr)); rotateRight(arr, 3); System.out.println("After k=3: " + Arrays.toString(arr)); // [5,6,7,1,2,3,4] // Edge case: k >= n int[] arr2 = {1, 2, 3, 4, 5}; rotateRight(arr2, 7); // 7 % 5 = 2 System.out.println("k=7 (effective k=2): " + Arrays.toString(arr2)); // [4,5,1,2,3] // Edge case: k = n (full cycle) int[] arr3 = {1, 2, 3}; rotateRight(arr3, 3); // 3 % 3 = 0, no change System.out.println("k=3 (full cycle): " + Arrays.toString(arr3)); // [1,2,3] } }
After k=3: [5, 6, 7, 1, 2, 3, 4]
k=7 (effective k=2): [4, 5, 1, 2, 3]
k=3 (full cycle): [1, 2, 3]
- Step 1 (reverse all): puts tail at front, head at back — both reversed internally.
- Step 2 (reverse first k): fixes the internal order of the new front section.
- Step 3 (reverse last n-k): fixes the internal order of the new back section.
- Each element is touched at most twice. O(n) total swaps.
- No extra memory — all reversals are in-place with a single temp variable.
Brute Force: Rotate One Step at a Time, k Times
The most natural instinct is to rotate the array one position at a time, repeating that k times. For a single right rotation, you save the last element, shift every other element one spot to the right, then place the saved element at index 0. It's easy to reason about because you can watch it happen step by step.
The cost, though, is brutal. For each of the k rotations you're touching every n elements, giving you O(n × k) time. On a 10-element array rotated 3 times that's 30 operations — fine. On a 100,000-element array rotated 50,000 times, that's 5 billion operations. This approach is only acceptable when you know k is tiny and n is small.
There's also a subtle correctness trap here: what if k is larger than n? Rotating a 5-element array by 7 is the same as rotating it by 2 (because after 5 rotations you're back where you started). Always reduce k with k = k % n before you start, or you'll do redundant full loops.
package io.thecodeforge.algo; import java.util.Arrays; public class BruteForceRotation { /** * Rotates the array to the right by 'steps' positions. * Strategy: perform a single-step rotation exactly 'steps' times. * Time: O(n * steps) * Space: O(1) */ public static void rotateRight(int[] elements, int steps) { int length = elements.length; if (length == 0) return; steps = steps % length; for (int round = 0; round < steps; round++) { int displaced = elements[length - 1]; for (int i = length - 1; i > 0; i--) { elements[i] = elements[i - 1]; } elements[0] = displaced; } } public static void main(String[] args) { int[] schedule = {1, 2, 3, 4, 5, 6, 7}; System.out.println("Before end up with the original array — correct result, wasted work. If k > n you've done multiple unnecessary full cycles. Always reduce k before the outer loop. This one line separates a beginner's solution from a professional one.
Extra Array Approach: Trade Memory for Clarity
The extra-array approach is the 'obvious' O(n) time solution. You allocate a second array of the same size, calculate each element's destination index with the formula (i + k) % n, copy everything into the right spot, then copy it back. It's clean, readable, and easy to verify by hand.
The destination formula is worth understanding properly. When you rotate right by k, the element currently at index i should land at index (i + k) % n. The modulo handles the wrap-around automatically — no special case needed for elements near the tail.
The trade-off is O(n) extra space. In most application code that's completely acceptable — a few kilobytes for a scheduling array won't move the needle. But in memory-constrained environments (embedded systems, high-frequency trading with tight heap budgets, or the constraints of a LeetCode problem that says 'O(1) space'), this is disqualifying. Knowing when the extra allocation is fine versus when it's a problem is itself a sign of engineering maturity.
package io.thecodeforge.algo; import java.util.Arrays; public class ExtraArrayRotation { /** * Rotates the array to the right by 'steps' positions using a temporary buffer. * Time: O(n) * Space: O(n) */ public static void rotateRight(int[] elements, int steps) { int length = elements.length; if (length == 0) return; steps = steps % length; if (steps == 0) return; int[] buffer = new int[length]; for (int i = 0; i < length; i++) { int destination = (i + steps) % length; buffer[destination] = elements[i]; } System.arraycopy(buffer, 0, elements, 0, length); } public static void main(String[] args) { int[] trackIds = {101, 102, 103, 104, 105}; System.out.println("Before: " + Arrays.toString(trackIds)); rotateRight(trackIds, 2); System.out.println("After k=2: " + Arrays.toString(trackIds)); // Verify: index 0 (101) -> (0+2)%5=2, index 3 (104) -> (3+2)%5=0 (wraps) } }
After k=2: [104, 105, 101, 102, 103]
- Right rotation destination: (i + k) % n. Element at index i moves forward by k.
- Left rotation destination: (i - k by k.
- The +n in left rotation prevents negative indices. Java: -1 % 5 = -1 (not 4).
- This formula is the basis of circular buffer access without physical rotation.
- Memorize it — it appears in round-robin schedulers, Caesar ciphers, and ring buffers.
The Reversal Trick: O(n) Time, O(1) Space — and Why It Works
This is the approach that separates people who've thought deeply about arrays from those who haven't. It uses three in-place reversals to achieve a full rotation without any extra memory. It feels like magic the first time you see it, but the logic is completely provable.
Here's the insight. A right rotation by k means the last k elements move to the front, and the first n-k elements shift to the back. Think of the array in two chunks: the 'tail' (last k elements) and the 'head' (first n-k elements). If you reverse the entire array, then reverse each chunk independently, you get the rotated result.
Why? Reversing the whole array puts the tail at the front and the head at the back — but both in reversed order. Reversing each section independently corrects the internal order within each chunk. The two reversal errors cancel each other out. It's like turning a sock inside-out twice to clean it: two inversions restore the original orientation, but in a new overall position.
This is the solution LeetCode expects for their 'Rotate Array' problem (problem 189) when they ask for O(1) extra space, and it's the answer that lands job offers.
package io.thecodeforge.algo; import java.util.Arrays; public class ReversalTrickRotation { /** * Rotates the array to the right by 'steps' positions using three reversals. * Time: O(n) — each element is touched at most twice * Space: O(1) — in-place swaps */ public static void rotateRight(int[] elements, int steps) { int length = elements.length; if (length == 0) return; steps = steps % length; if (steps == 0) return; reverse(elements, 0, length - 1); reverse(elements, 0, steps - 1); reverse(elements, steps, length - 1); } private static void reverse(int[] elements, int from, int to) { while (from < to) { int temp = elements[from]; elements[from] = elements[to]; elements[to] = temp; from++; to--; } } public static void main(String[] args) { int[] taskQueue = {1, 2, 3, 4, 5, 6, 7}; System.out.println("Original: " + Arrays.toString(taskQueue)); rotateRight(taskQueue, 3); System.out.println("After k=3: " + Arrays.toString(taskQueue)); int[] servers = {101, 102, 103, 104, 105}; rotateRight(servers, 1); System.out.println("After k=1: " + Arrays.toString(servers)); int[] pixels = {255, 128, 64, 32, 16}; rotateRight(pixels, 12); // 12 % 5 = 2 System.out.println("k=12 (effective k=2): " + Arrays.toString(pixels)); } }
After k=3: [5, 6, 7, 1, 2, 3, 4]
After k=1: [105, 101, 102, 103, 104]
k=12 (effective k=2): [32, 16, 255, 128, 64]
- Trace [1,2,3,4,5] k=2: [5,4,3,2,1] → [4,5,3,2,1] → [4,5,1,2,3].
- Trace [1,2,3,4,5,6,7] k=3: [7,6,5,4,3,2,1] → [5,6,7,4,3,2,1] → [5,6,7,1,2,3,4].
- Each step is a simple two-pointer swap. No complex index arithmetic.
- The global mirror + local un-mirrors is the key insight.
- If asked 'why not just reverse the two halves without the global reverse?' — the halves are not adjacent after rotation. The global reverse brings them into position.
Cyclic Replacement: Move Each Element Directly to Its Final Position
Cyclic replacement is an alternative O(n) time, O(1) space approach. Instead of three reversals, it moves each element directly to its final position in cycles. Start at index 0, move the element to (0 + k) % n, save the displaced element, move it to its final position, and so on until you return to index 0. Then start the next cycle at index 1.
The number of cycles is gcd(n, k) — the greatest common divisor of the array length and the rotation step. Each cycle processes n / gcd(n, k) elements. Total elements processed: gcd(n, k) * (n / gcd(n, k)) = n. O(n) time, O(1) space.
This approach is harder to implement correctly than the reversal trick because you must track when a cycle completes (when you return to the starting index). The reversal trick is preferred in interviews for its simplicity. Cyclic replacement is useful when you need to understand the mathematical structure of the rotation.
package io.thecodeforge.algo; import java.util.Arrays; public class CyclicReplacementRotation { /** * Rotates the array right by k positions using cyclic replacement. * Time: O(n) — each element is moved exactly once * Space: O(1) — only one temp variable */ public static void rotateRight(int[] arr, int k) { int n = arr.length; if (n == 0) return; k = k % n; if (k == 0) return; int count = 0; // total elements moved for (int start = 0; count < n; start++) { int current = start; int prev = arr[start]; do { int next = (current + k) % n; int temp = arr[next]; arr[next] = prev; prev = temp; current = next; count++; } while (current != start); } } public static void main(String[] args) { int[] arr = {1, 2, 3, 4, 5, 6, 7}; System.out.println("Before: " + Arrays.toString(arr)); rotateRight(arr, 3); System.out.println("After k=3: " + Arrays.toString(arr)); // With gcd(n,k) = gcd(6,2) = 2 cycles, each of length 3 int[] arr2 = {1, 2, 3, 4, 5, 6}; rotateRight(arr2, 2); System.out.println("After k=2: " + Arrays.toString(arr2)); } }
After k=3: [5, 6, 7, 1, 2, 3, 4]
After k=2: [5, 6, 1, 2, 3, 4]
- Reversal trick: simpler code, easier to debug. Preferred in interviews.
- Cyclic replacement: each element moved exactly once. More elegant mathematically.
- Number of cycles = gcd(n, k). Each cycle processes n/gcd(n,k) elements.
- Cyclic replacement is harder to implement correctly (cycle completion tracking).
- Both achieve O(n) time, O(1) space. Choose based on code simplicity.
| Aspect | Brute Force (One-by-One) | Extra Array (Buffer) | Reversal Trick (In-Place) | Cyclic Replacement |
|---|---|---|---|---|
| Time Complexity | O(n * k) | O(n) | O(n) | O(n) |
| Space Complexity | O(1) | O(n) | O(1) | O(1) |
| Code Readability | Very easy to follow | Easy to follow | Requires explanation | Hard to get right |
| Handles k > n? | Only with modulo guard | Only with modulo guard | Only with modulo guard | Only with modulo guard |
| Best Use Case | Tiny arrays, tiny k | When memory isn't a constraint | Production / interview code | Low-level optimization |
| Mutation of input? | Yes (in-place) | Yes (copies buffer back) | Yes (in-place) | Yes (in-place) |
| Risk of TLE on large n? | High when k is large | None | None | None |
| LeetCode 189 optimal? | No — TLE on large inputs | Partially (fails O(1) space) | Yes — accepted optimal | Yes — accepted optimal |
| Elements touched | n per step, n*k total | n (write to buffer) + n (copy back) | At most 2n (each reversal touches elements) | Exactly n (each element moved once) |
🎯 Key Takeaways
- Always reduce k with k % n before rotating — skipping this turns an O(n) algorithm into O(n²) or causes silent redundant full cycles.
- The reversal trick works because two inversions cancel out: reversing the whole array then reversing each segment independently restores internal order in a new overall position.
- The destination-index formula (i + k) % n loop when gcd(n, k) > 1 and the cycle does not return to the starting index — Fix: use a count variable to track total elements moved, and terminate is reusable across circular buffers, Caesar ciphers, and round-robin schedulers — memorise it, not just for rotations.
- Choose your approach based on constraints: brute force for throwaway scripts with tiny data, extra array when memory is free and clarity matters, reversal trick for production systems and any interview that mentions O(1) space.
- Cyclic replacement moves each element exactly once vs twice in the reversal trick. It is mathematically elegant but harder to implement correctly.
- For left rotation: reverse first k, reverse last n-k, then reverse all. The order of reversals matters — it is different from right rotation.
- Test with k = n (full cycle), k = n+1 (one past full cycle), k = 0 (no rotation), and k > n (multiple full cycles). All must produce correct results.
- In production, consider circular buffer pointer offsets for O(1) rotation without any element movement. Physical rotation is only needed when the rotated order must be persisted.
⚠ Common Mistakes to Avoid
Interview Questions on This Topic
- QCan you rotate an array in-place using O(1) extra space? Walk me through your approach and prove it's correct with a small example.
- QWhat happens when k is greater than or equal to the array length? How does your solution handle that edge case, and why is the modulo operation the right fix?
- QIf I asked you to rotate left instead of right, how would you adapt your reversal-trick solution? What changes in the boundary indices, and can you derive the formula for the left-rotation destination index?
- QCompare the reversal trick with cyclic replacement. Both achieve O(n) time, O(1) space. When would you choose one over the other?
- QHow would you implement a circular buffer that supports O(1) rotation without physically moving elements? What is the relationship between the rotation step and the read pointer offset?
- QYour round-robin load balancer rotates a server pool by 1 slot per request. After 16 requests, traffic is unevenly distributed. What is the most likely root cause?
- QHow would you rotate a 2D matrix 90 degrees clockwise? How does this relate to array rotation?
- QExplain the gcd(n, k) relationship in cyclic replacement. Why does the number of cycles equal gcd(n, k)?
- QHow would you rotate an array in-place without using any extra variable (not even a temp for swapping)? Is this possible?
- QDescribe a production scenario where array rotation choice impacted performance. What approach did you use and what was the GC/cache impact?
Frequently Asked Questions
What is the most efficient way to rotate an array in Java?
The reversal trick achieves O(n) time and space — the theoretical k elements wrap to the back. For a right rotation the destination formula is (i + k) % n; for left rotation it's (i - k + n) % n. The +n in the left-rotation formula prevents negative indices.
Why does rotating an array by k give the same result as rotating by k % n?
After exactly n rotations (where n is the array length), every element has returned to its original position — you've done a full cycle. Any additional rotations beyond a full cycle just repeat positions you've already visited. So rotating by 7 on a 5-element array is identical to rotating by 7 % 5 = 2. Applying the modulo before your algorithm is a correctness and performance guard in one.
Why do we normalize k = k % n?
Rotating by n is a no-op. Rotating by n+2 is the same as rotating by 2. Normalizing with % n eliminates full rotations, ensuring we do the minimum work. Without it, we might reverse far more elements than necessary for large k values.
How does a cyclic replacement approach compare to the reverse trick?
Cyclic replacement also achieves O(n) time and O(1) space. It moves each element directly to its final position in cycles. The reverse trick is generally simpler to implement and reason about, so it is preferred in interviews.
Can you rotate an array without any element movement?
Yes — use a circular buffer with a pointer offset. Instead of moving elements, maintain a read pointer that starts at offset k. Access element i at position (i + k) % n. This is O(1) per access and O(1) per rotation (just increment the offset). The physical array is never modified. This is how production circular buffers work.
What is the relationship between gcd(n, k) and cyclic replacement?
The number of cycles in cyclic replacement equals gcd(n, k). Each cycle processes n / gcd(n, k) elements. Total elements processed: gcd(n, k) * (n / gcd(n, k)) = n. When gcd(n, k) = 1 (n and k are coprime), there is exactly one cycle. When gcd(n, k) = n (k is a multiple of n), each element is its own cycle (no movement needed after normalization).
Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.