skip to content

How does the three-reversal trick rotate an array by k positions in place?

level: middleimportance: must knowfreq 66%

answer

  1. picture the array as two blocks
  2. reversing a concatenation flips block order
  3. one big reverse then two small ones
  4. repair each block's internal order
  5. reduce k before touching any index

basics

~20 s

Reverse the whole array, then its first k elements and its last n-k. The full reverse brings the last k entries to the front but backwards; the two partial reverses repair each block's order, using no extra array.

solid answer

~40 s

Think of the array as two blocks, `X` (the first n-k entries) and `Y` (the last k). A right rotation by k wants `Y X`. Reversing the whole array yields `reverse(Y) reverse(X)` — the blocks are already in the right order, each internally backwards — so `reverse(a, 0, k-1)` and `reverse(a, k, n-1)` repair them, giving `Y X`. On an on-call roster of five weeks rotated right by two: `[A,B,C,D,E]` becomes `[E,D,C,B,A]`, then `[D,E,C,B,A]`, then `[D,E,A,B,C]`. Three sequential passes, n swaps and 2n element writes in total, O(1) extra space. The mandatory precondition is `k = k mod n` first: with k larger than n, the first partial reversal indexes past the end. After reduction, k = 0 works out as a no-op provided your range reversal tolerates an empty range.

go deeper

for a junior

Be ready to state the three steps in order and trace them on a five-element example, and to say that the result uses no second array.

for a middle

Explain why it works using the block identity — reversing a concatenation flips the block order — and name the modulo reduction as a precondition rather than an optimisation.

for a senior

Show the degenerate probes you would test first (k = 0, k = n, k just over n, n = 1) and explain why an empty-range-safe reversal primitive removes every special case.

for a principal

Be able to defend the linear-time, constant-extra-space combination against a simple copy when peak memory on a fleet, not runtime, is the constraint being priced.

## The trick To rotate an n-element array right by k (the last k entries move to the front, the rest shift back), do three range reversals: 1. `reverse(a, 0, n-1)` — the whole array 2. `reverse(a, 0, k-1)` — the first k entries 3. `reverse(a, k, n-1)` — the remaining n-k entries Nothing else. No second array, no shifting loop. ## Why it works — the block identity View the array as the concatenation of two blocks: `X` of length n-k and `Y` of length k, so the array is `X Y`. A right rotation by k is exactly `Y X`. The identity that drives everything is: **reversing a concatenation reverses the blocks and swaps their order.** Formally, `reverse(X Y) = reverse(Y) reverse(X)`. So step 1 produces `reverse(Y) reverse(X)`. The blocks are now in the order you want — `Y`'s material before `X`'s — but each is internally backwards. Step 2 reverses the first k slots, which hold `reverse(Y)`, restoring `Y`. Step 3 reverses the last n-k slots, which hold `reverse(X)`, restoring `X`. The result is `Y X`. ## A trace worth memorising An on-call roster `[A, B, C, D, E]` rotated right by k = 2 should become `[D, E, A, B, C]` — the last two weeks move to the front. - After reversing everything: `[E, D, C, B, A]` - After reversing the first 2: `[D, E, C, B, A]` - After reversing the last 3: `[D, E, A, B, C]` Doing the three reversals in the other order — first k, then the rest, then the whole array — gives a *left* rotation by k. Both orderings are correct algorithms for different conventions, which is why an interviewer will ask which direction yours produces. Always state the convention before you write. ## The failure this question hunts for The k values that break naive code are the ones outside `[0, n)`. - **k >= n.** `reverse(a, 0, k-1)` walks its right pointer to index k-1, which is past the last element. Depending on the environment that is a crash or silent corruption of neighbouring memory. Fix it before the first reversal: `k = k mod n`. - **k = 0 or k = n.** After reduction both mean "do not move anything", and the trick handles them correctly *if* your range reversal is a no-op on an empty or single-element range. With k = 0, step 2 gets the empty range `[0, -1]` and step 3 undoes step 1. With k = n before reduction, step 2 reverses everything back and step 3 gets the empty range `[n, n-1]`. Neither needs a special case; both need a range reversal that checks `lo < hi` rather than assuming a non-empty range. - **Negative k**, if your interface allows a rotation the other way, needs normalising with `k = ((k mod n) + n) mod n`, because a plain remainder can stay negative. ## Cost, precisely Each element is written exactly twice: once by the full reversal, once by whichever partial reversal covers its new home. That is 2n writes, or n swaps, across three passes. Time is O(n) and auxiliary space is O(1) — the reversals need only their two indices and a swap temporary. Compare the obvious alternatives. Copying into a fresh array of size n and reading back is O(n) time but O(n) extra space, and for a large array that means doubling peak memory. Rotating by repeated single-position shifts is O(n) space-free but O(n*k) time, which becomes quadratic when k grows with n. The three-reversal trick is the one that is linear in time *and* constant in extra space, which is precisely the pair of constraints interviewers set up when they say "in place". ## Why it survives contact with real hardware All three passes are two-pointer scans converging from the ends of a contiguous range, so every touch is sequential and predictable. That is why the trick remains the default even though it writes each element twice: the write count is not the binding cost on large data, the memory access pattern is. The same reason makes it easy to reason about in review — three calls to a primitive you already trust, with no index arithmetic to get subtly wrong beyond the modulo.

  • Exactly where does the code break if k is larger than n?
    At the second reversal. It is asked to reverse the range `[0, k-1]`, and `k-1` is past the final index, so the right pointer starts out of range and the first swap touches memory that is not yours. Reducing with `k = k mod n` before any reversal removes the whole class of failure, and negative inputs need `((k mod n) + n) mod n`.
  • How many element writes does the three-reversal rotation perform?
    About 2n. The full reversal writes every element once, and each element is then written once more by exactly one of the two partial reversals, since the partial ranges are disjoint and cover the array. That is n swaps across three passes — twice the writes of a cycle-based rotation, and still usually the faster choice on large data because all three passes are sequential.
  • Does the order of the three reversals change the result?
    Yes, it changes the direction. Whole array first, then the first k and the rest, produces a right rotation by k. Doing the first k and the rest first, then reversing the whole array, produces a left rotation by k. Since a right rotation by k equals a left rotation by n-k, either convention can express the other — but you must say which one you are implementing.

Reversing every letter of the phrase 'on call' gives 'llac no': the two words have traded places, each spelled backwards. Reversing each word in place then repairs the spelling.

saying these in an interview costs you the question

  • Assumes k is always smaller than n
  • Says the trick needs an O(n) temporary buffer
  • Calls it O(n log n) because there are three passes
  • Cannot say whether the result rotates left or right
  • Adds special cases for k = 0 and k = n
  • Reaches for repeated single-position shifts instead

context