Search in Rotated Sorted Array — The Binary Search Problem Every FAANG Interviewer Loves

Sanjeev SharmaSanjeev Sharma
15 min read

Advertisement

Problem Statement

Given an integer array nums sorted in ascending order (with distinct values) that has been rotated at an unknown pivot index k, and a target value, return the index of target in nums, or -1 if it is not present.

You must write an algorithm with O(log n) runtime complexity.

Example 1:

Input:  nums = [4,5,6,7,0,1,2], target = 0
Output: 4

Example 2:

Input:  nums = [4,5,6,7,0,1,2], target = 3
Output: -1

Example 3:

Input:  nums = [1], target = 0
Output: -1

Constraints:

  • 1 <= nums.length <= 5000
  • -10^4 <= nums[i] <= 10^4
  • All values in nums are unique
  • nums is an ascending array rotated at some pivot
  • -10^4 <= target <= 10^4

Why This Problem Matters

LeetCode 33 is not just a medium-difficulty problem — it is one of the most reliably asked binary search questions across Google, Meta, Amazon, and Microsoft interviews. A search of interview reports on Blind, Glassdoor, and LeetCode discussion threads repeatedly surfaces this problem under real interview conditions.

The reason it appears so often is not because it is tricky for its own sake. It is because this problem exposes whether a candidate truly understands binary search at a conceptual level, or whether they have only memorized the template.

Standard binary search works because the array is sorted, and being sorted means you can make a binary decision at the midpoint: the target is either in the left half or the right half, and you can tell which with a single comparison. The moment you rotate the array, that direct comparison breaks — nums[mid] no longer tells you which direction to move in an obvious way.

To recover O(log n), you need to discover a new invariant that still lets you throw away half the search space at each step. That invariant is the subject of this entire article, and understanding it deeply is what separates candidates who solve this cleanly from those who fumble through it.

Interviewers also love this problem because it opens natural doors to follow-up questions: What if there are duplicates? What if you need the rotation index itself? What if the matrix is 2D? Candidates who truly understand the invariant can answer these extensions on the fly. Those who have memorized code cannot.

The Core Observation

Before writing a single line of code, pause and think about what rotation actually does to an array.

Start with a fully sorted array: [0, 1, 2, 4, 5, 6, 7]

Rotate it at index 3 (shift the first three elements to the end): [4, 5, 6, 7, 0, 1, 2]

Now pick the midpoint: mid = 3, so nums[mid] = 7.

Look at the left half: [4, 5, 6, 7]. This is perfectly sorted. Look at the right half: [7, 0, 1, 2]. This contains the rotation point — it is not sorted.

Now try a different pivot. Rotate the original array at index 5: [6, 7, 0, 1, 2, 4, 5]

Midpoint: mid = 3, nums[mid] = 1.

Left half: [6, 7, 0, 1]. Contains the rotation point — not sorted. Right half: [1, 2, 4, 5]. Perfectly sorted.

Notice the pattern? The rotation point can only exist in one half at a time. The pivot is a single break in the ordering. When you split the array at any midpoint, that break lands in exactly one of the two halves. The other half has no break — it is entirely sorted.

This is the invariant: at every step of binary search on a rotated sorted array, at least one half is always fully sorted.

It does not matter where the pivot is. It does not matter how many times the array has been rotated conceptually. As long as there are distinct values and a single rotation point, splitting at any midpoint always guarantees one sorted half.

This is the key that unlocks everything.

The Decision Tree

Once you know that one half is always sorted, the algorithm becomes clear. Here is the decision logic written out as a tree:

Compute mid = (left + right) / 2

If nums[mid] == target → return mid (done)

Check which half is sorted:
  IF nums[left] <= nums[mid]:
The LEFT half [left..mid] is sorted
    Check if target falls in this sorted range:
      IF nums[left] <= target < nums[mid]:
Target could be here → search LEFT (right = mid - 1)
      ELSE:
Target is NOT in left half → search RIGHT (left = mid + 1)
  ELSE:
The RIGHT half [mid..right] is sorted
    Check if target falls in this sorted range:
      IF nums[mid] < target <= nums[right]:
Target could be here → search RIGHT (left = mid + 1)
      ELSE:
Target is NOT in right half → search LEFT (right = mid - 1)

If left > right → return -1 (not found)

The critical insight is this: you are not searching the sorted half because it is sorted. You are using the sorted half to eliminate it or the other half. In a sorted subarray, you can check membership with two comparisons. If the target cannot be in the sorted half (because it falls outside the range), it must be in the other half — and you search there instead.

This is fundamentally different from standard binary search. You are always moving toward the answer, but the evidence you use to make the move is "which half is sorted" rather than "is the target bigger or smaller than mid."

Step-by-Step Algorithm (Pseudocode)

function search(nums, target):
    left = 0
    right = len(nums) - 1

    while left <= right:
        mid = left + (right - left) / 2   # avoids integer overflow

        if nums[mid] == target:
            return mid

        # Determine which half is sorted
        if nums[left] <= nums[mid]:         # left half is sorted
            if nums[left] <= target < nums[mid]:
                right = mid - 1            # search left half
            else:
                left = mid + 1             # search right half
        else:                              # right half is sorted
            if nums[mid] < target <= nums[right]:
                left = mid + 1             # search right half
            else:
                right = mid - 1            # search left half

    return -1   # target not found

Why nums[left] <= nums[mid] and not <?

When left == mid (array of size 1 or 2), nums[left] == nums[mid] and the left half is trivially "sorted" (it contains just one element). Using <= correctly handles this edge case. Using < would misclassify it and cause incorrect behavior.

Why nums[left] <= target < nums[mid] (strict < on the right)?

Because nums[mid] has already been checked and ruled out. We need the target to be strictly less than nums[mid] to place it in the left half.

Visual Dry Run

Let us trace through nums = [4, 5, 6, 7, 0, 1, 2], target = 0 step by step.

Initial state:

Index:  0  1  2  3  4  5  6
Array: [4, 5, 6, 7, 0, 1, 2]
left=0, right=6

Iteration 1:

mid = (0 + 6) / 2 = 3
nums[mid] = nums[3] = 7

Is 7 == 0? No.

Is nums[left] <= nums[mid]?  →  nums[0]=4 <= nums[3]=7YES
LEFT half [4, 5, 6, 7] is sorted

Is target in left half range?4 <= 0 < 7?NO (0 < 4)
Target NOT in left half → search RIGHT
  → left = mid + 1 = 4

State after iteration 1: left=4, right=6


Iteration 2:

mid = (4 + 6) / 2 = 5
nums[mid] = nums[5] = 1

Is 1 == 0? No.

Is nums[left] <= nums[mid]?  →  nums[4]=0 <= nums[5]=1YES
LEFT half [0, 1] is sorted

Is target in left half range?0 <= 0 < 1?YES
Target could be in left half → search LEFT
  → right = mid - 1 = 4

State after iteration 2: left=4, right=4


Iteration 3:

mid = (4 + 4) / 2 = 4
nums[mid] = nums[4] = 0

Is 0 == 0?  YESreturn 4

Result: index 4. Correct.

The algorithm made only 3 iterations on a 7-element array. No wasted comparisons. Every step either found the target or eliminated half the remaining search space.

Common Mistakes

Mistake 1: Using < instead of <= to identify the sorted half

# WRONG
if nums[left] < nums[mid]:

# CORRECT
if nums[left] <= nums[mid]:

When left == mid (which happens when the window shrinks to two elements), nums[left] == nums[mid]. If you use strict <, you fall into the else branch and treat the right half as sorted — even though the left half actually contains just the single element at left=mid. This can push left in the wrong direction.

Mistake 2: Not including the boundary values in the range check

# WRONG — misses the case where target == nums[left]
if nums[left] < target < nums[mid]:

# CORRECT
if nums[left] <= target < nums[mid]:

The sorted left half includes nums[left] itself. If target equals nums[left], it belongs to the left half. Similarly for the right half, nums[right] is included.

Mistake 3: Forgetting the non-rotated case

An array that has never been rotated — or has been rotated by exactly n positions — is simply a sorted array. Your code must handle nums = [1, 2, 3, 4, 5] without rotation. If you write nums[left] <= nums[mid], the non-rotated case naturally satisfies this condition (the entire left half is sorted), and the algorithm degrades gracefully to standard binary search. Do not add special-case logic for this — it is handled implicitly.

Mistake 4: Moving to mid instead of mid ± 1

# WRONG — causes infinite loop when left == mid
right = mid

# CORRECT
right = mid - 1

Once you have ruled out nums[mid] as the target, never include it in the next search window. Setting right = mid instead of right = mid - 1 can trap the loop when left == mid, oscillating forever without making progress.

Solutions

Python

class Solution:
    def search(self, nums: list[int], target: int) -> int:
        left, right = 0, len(nums) - 1

        while left <= right:
            mid = left + (right - left) // 2  # safe midpoint (no overflow)

            if nums[mid] == target:
                return mid

            # Determine which half is sorted by comparing left boundary to mid
            if nums[left] <= nums[mid]:
                # Left half [left..mid] is fully sorted
                if nums[left] <= target < nums[mid]:
                    # Target falls within the sorted left range
                    right = mid - 1
                else:
                    # Target is outside left range; must be in right half
                    left = mid + 1
            else:
                # Right half [mid..right] is fully sorted
                if nums[mid] < target <= nums[right]:
                    # Target falls within the sorted right range
                    left = mid + 1
                else:
                    # Target is outside right range; must be in left half
                    right = mid - 1

        return -1  # target not found

JavaScript

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number}
 */
function search(nums, target) {
    let left = 0;
    let right = nums.length - 1;

    while (left <= right) {
        // Compute midpoint safely to avoid integer overflow
        const mid = left + Math.floor((right - left) / 2);

        if (nums[mid] === target) {
            return mid;
        }

        // Check which half is sorted
        if (nums[left] <= nums[mid]) {
            // Left half [left..mid] is fully sorted
            if (nums[left] <= target && target < nums[mid]) {
                // Target is within the sorted left half
                right = mid - 1;
            } else {
                // Target is not in the left half; search right
                left = mid + 1;
            }
        } else {
            // Right half [mid..right] is fully sorted
            if (nums[mid] < target && target <= nums[right]) {
                // Target is within the sorted right half
                left = mid + 1;
            } else {
                // Target is not in the right half; search left
                right = mid - 1;
            }
        }
    }

    return -1; // target not found
}

Both solutions are clean, well-commented, and follow the same logic. The only language-specific detail is using Math.floor in JavaScript (since / does not truncate integers) and // for integer division in Python.

Complexity Analysis

MetricValueReason
TimeO(log n)Each iteration halves the search space; at most log₂(n) iterations
SpaceO(1)Only constant extra variables (left, right, mid)

The O(log n) guarantee holds because we always eliminate at least half the remaining array at each step, regardless of where the rotation point falls. This matches the constraint stated in the problem.

Follow-Up Questions

LC 81 — Search in Rotated Sorted Array II (with duplicates)

The problem becomes significantly harder when duplicates are allowed. Consider the array [1, 3, 1, 1, 1] and target 3. When nums[left] == nums[mid] == nums[right], you cannot determine which half is sorted. You have no way to know if you are in the left plateau or the right one.

The fix: when nums[left] == nums[mid] == nums[right], increment left and decrement right by one and try again. This safely shrinks the window, but in the worst case (e.g., [1,1,1,1,1,1,1] searching for 2), you shrink by only one element per iteration.

Worst-case time complexity degrades to O(n). This is a fundamental mathematical limit when duplicates obscure the sorted half — you have no choice but to check each element individually in that worst case.

LC 153 — Find Minimum in Rotated Sorted Array

Instead of searching for a target, find the minimum element. The same "one half is always sorted" insight applies. The minimum is always at the start of the unsorted half. When nums[mid] > nums[right], the minimum is in the right half. Otherwise, it is in the left half (and nums[mid] could itself be the minimum, so use right = mid, not right = mid - 1).

LC 154 — Find Minimum in Rotated Sorted Array II (with duplicates)

Same as LC 153 but with duplicates. Same problem as LC 81: when nums[mid] == nums[right], you cannot determine which half contains the minimum, so you decrement right by one. Worst case degrades to O(n) again.

Find the Rotation Index Itself

This is essentially LC 153. The rotation index is the index of the minimum element. Once found, you know the full structure of the array and can perform two independent standard binary searches (one on each sorted subarray).

How Interviewers Probe Your Understanding

Expect these questions after you present your solution:

  1. "What if the array has duplicates?" — This tests whether you understand why your current key comparison (nums[left] <= nums[mid]) breaks with duplicates. The correct answer involves acknowledging the worst-case O(n) degradation.

  2. "Why <= instead of < when checking the sorted half?" — This directly tests your understanding of the edge case where left == mid.

  3. "Can you solve it without the <= in the range check on the right boundary?" — Pushes you to reason about whether the strict inequality is strictly necessary.

  4. "What happens if the entire array is non-rotated?" — Tests that your solution handles the identity rotation gracefully without special casing.

  5. "What is the loop invariant?" — The invariant is: if target exists in nums, it exists in nums[left..right]. The loop preserves this invariant at every step.

This Pattern Solves

The "identify the sorted half" pattern generalizes well beyond this single problem:

  • Find Peak Element (LC 162) — use a similar "which side is ascending" comparison to binary search for the peak
  • Search a 2D Matrix (LC 74, LC 240) — structured binary elimination on non-flat sorted structures
  • Find in Mountain Array (LC 1095) — binary search for the peak first, then binary search each side
  • Koko Eating Bananas (LC 875) / Capacity to Ship Packages (LC 1011) — binary search on answer space rather than array contents, but the same "eliminate half the space per step" discipline

Any time you need O(log n) on a structure that is not perfectly sorted but has a local sorted guarantee, this family of techniques applies.

Key Takeaway

The insight that unlocks this problem — one half of the array is always sorted after any rotation — is what separates a clean O(log n) solution from a confused O(n) attempt. Once you internalize that a single rotation point can only break ordering in one half at a time, the algorithm writes itself: identify the sorted half, use range checks to determine where the target must be, and eliminate the impossible half. Every follow-up in this family (duplicates, finding the minimum, finding the rotation point) is simply a variation of the same core invariant, and mastering this one problem gives you the mental model to solve all of them without rememorizing new code.

Advertisement

Sanjeev Sharma

Written by

Sanjeev Sharma

Full Stack Engineer · E-mopro