skip to content

Why does a wrap-point search in a rotated array shrink with hi = mid, not hi = mid - 1?

level: middleimportance: should knowfreq 55%

answer

  1. there is no target to return on
  2. ask which branch disqualifies the midpoint
  3. one side proves mid is not the smallest
  4. the other leaves mid still a candidate
  5. keeping mid forces a strict loop test

basics

~20 s

The midpoint itself may be the wrap point: when its value is not greater than the value at the high end, it stays a candidate for the smallest element, so discarding it can lose the answer.

solid answer

~50 s

The loop converges on the position of the smallest value — the wrap point — rather than on a target, so the shrink must never discard a live candidate. When `a[mid] > a[hi]`, the midpoint is provably *not* the minimum (something to its right is smaller), so `lo = mid + 1` is safe. When `a[mid] <= a[hi]`, everything from `mid` to `hi` is in order, which makes `mid` the smallest of that stretch and therefore still a candidate — hence `hi = mid`, keeping it. The asymmetry forces the loop test to be `while lo < hi` rather than `lo <= hi`: with `hi = mid` and `lo == hi`, the midpoint equals both bounds and the range would never shrink, so a `<=` test spins forever. The loop exits with `lo == hi` pointing at the wrap point.

code

pseudocode · 10 lines
pseudocode
// a: distinct keys, ascending, then rotated; find the smallest key's index
lo = 0
hi = length(a) - 1
while lo < hi
    mid = lo + (hi - lo) / 2
    if a[mid] > a[hi]
        lo = mid + 1          // a descent lies right of mid, so mid is out
    else
        hi = mid              // mid..hi is ascending, so mid still qualifies
return lo                     // lo == hi: the wrap point

go deeper

for a junior

Recall the shape: no target means no early return, so the loop narrows to one surviving index. Remember that one branch can drop the midpoint and the other cannot, and that the loop test is strict.

for a middle

Explain which branch disqualifies the midpoint and why, then show that pairing a midpoint-keeping shrink with a non-strict loop test hangs rather than answering wrongly. Name the exit state: one index, and it is the wrap point.

for a senior

Show how you would test it — every rotation of a small array plus the unrotated case — and be able to say what the offset is worth downstream: it converts every later lookup into an ordinary search on remapped indices.

for a principal

Frame it as build-versus-record: if the component that wraps the buffer can write down its own head position, no search is needed at all, and you have removed a class of boundary bugs from the codebase for free.

## The problem shape A fixed-size capture buffer records monotonically increasing sequence numbers and wraps when full, so a raw dump begins part-way round the ring: an ascending run, one drop, another ascending run. To replay the records in true order you need the index of the drop — the position of the smallest sequence number. That index *is* the rotation offset, so once you have it, the original position of any dumped entry `i` is `(i + offset) mod n`. This is a search with no target. There is no value to compare against, so the usual `found it, return` exit does not exist; instead the range is narrowed until one index survives. ## Why the two branches are asymmetric The loop maintains the invariant **the wrap point lies in `lo..hi`**, and each branch must preserve it. - `a[mid] > a[hi]`: a descent happens somewhere strictly after `mid`, so a value smaller than `a[mid]` exists to the right. The midpoint is therefore disqualified as the minimum, and `lo = mid + 1` both preserves the invariant and shrinks the range. - `a[mid] <= a[hi]`: no descent occurs between `mid` and `hi`, so that whole stretch is ascending and `a[mid]` is the smallest value in it. The minimum of the entire range is thus at `mid` or to its left — `mid` is still a candidate. Writing `hi = mid - 1` would throw away the only correct answer whenever the minimum sits exactly at `mid`, which happens routinely (it happens on the very first iteration of an unrotated array of odd length). Writing `hi = mid` keeps the candidate and still shrinks the range, because `mid < hi` whenever `lo < hi`. ## Why the loop test must be strict Because one branch keeps `mid`, progress is no longer guaranteed for a single-element range. With `while lo <= hi` and `lo == hi`, the midpoint equals both bounds; the comparison `a[mid] > a[hi]` is an equality-driven false, so `hi = mid` reassigns `hi` to the value it already had and the loop repeats identically — a hang, not a wrong answer. The strict test `while lo < hi` exits the moment the range holds one index, and that index is the wrap point. This pairing is the general rule worth internalising: **a branch that keeps the midpoint requires a strict loop condition**; a branch structure that always moves both bounds past the midpoint can use the non-strict one. Mixing a `hi = mid` shrink with a `lo <= hi` test is one of the most common infinite loops in this whole family of searches. ## Why the comparison is against the high endpoint specifically Here, unlike the target-search variant, the anchor choice is not merely stylistic. Comparing the midpoint with the *low* endpoint cannot distinguish two different situations: on an unrotated ascending range, `a[mid] > a[lo]` while the minimum sits at `lo`; on a rotated range with the wrap to the right of `mid`, `a[mid] > a[lo]` as well, and the minimum is to the right. The same observation would demand opposite moves, so the low-endpoint form needs an explicit pre-check (`if a[lo] < a[hi] then return lo`) before it becomes correct. The high-endpoint comparison needs no such guard: on an unrotated range `a[mid] <= a[hi]` always holds, the search keeps moving left, and it lands on index `0` — the right answer. ## Cost On distinct values the range at least halves every iteration, giving O(log n) time and O(1) extra space. For a capture buffer of a few thousand slots that is a dozen comparisons against a few thousand — a real but often irrelevant win, which is a fair thing to say out loud when asked whether you would ship it. ## Testing it honestly The inputs that catch the classic bugs are small and cheap to enumerate: every rotation offset of a four- and five-element array, including offset zero; a single-element range; a two-element range in both rotations. If a solution survives all rotations of a five-element array plus the unrotated case, the boundary logic is almost certainly right; if it only ever gets tried on one hand-picked rotation, an off-by-one can live in it indefinitely.

  • What goes wrong if you keep hi = mid but write the loop as while lo <= hi?
    It hangs. Once the range narrows to a single index, the midpoint equals both bounds, the comparison sends you into the branch that assigns `hi = mid`, and the state is unchanged — the loop repeats forever. Any branch that retains the midpoint must be paired with the strict `lo < hi` test that exits at one surviving index.
  • Why compare the midpoint with the high endpoint rather than the low one here?
    Because the low-endpoint comparison cannot tell an unrotated range from one whose wrap lies to the right — both give `a[mid] > a[lo]` while demanding opposite moves. The high-endpoint form handles the unrotated case naturally: the test always holds, the range keeps shrinking leftward, and it lands on index zero.
  • Once you have the wrap index, what do you do with it?
    It is the rotation offset, so replaying the dump in true order means visiting index `(i + offset) mod n` for `i` from zero upward. It also converts any subsequent lookup into an ordinary search on the unrotated index space, which is often cheaper than repeating the rotation-aware logic on every query.

saying these in an interview costs you the question

  • Mirrors ordinary binary search and always writes mid plus or minus one
  • Pairs a midpoint-keeping shrink with a non-strict loop test
  • Compares the midpoint with the low endpoint and breaks on an unrotated range
  • Cannot say what the loop returns when the range holds one index
  • Believes the wrap point must be found before any search is possible

context