LeetCode #34 — Medium

Find First and Last Position
of Element in Sorted Array

A complete visual walkthrough of the two-binary-search approach — finding both boundaries in O(log n) time.

Solve on LeetCode
The Problem

Problem Statement

Given an array of integers nums sorted in non-decreasing order, find the starting and ending position of a given target value.

If target is not found in the array, return [-1, -1].

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

Example 1:

Input: nums = [5,7,7,8,8,10], target = 8
Output: [3,4]

Example 2:

Input: nums = [5,7,7,8,8,10], target = 6
Output: [-1,-1]

Example 3:

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

Constraints:

  • 0 <= nums.length <= 105
  • -109 <= nums[i] <= 109
  • nums is a non-decreasing array.
  • -109 <= target <= 109
Patterns Used

Roadmap

  1. Brute Force — Linear Scan
  2. The Core Insight — Two Binary Searches Diverge on Equality
  3. Walkthrough — Both Searches on [5,7,7,8,8,10]
  4. Edge Cases
  5. Full Annotated Code
  6. Interactive Demo
  7. Complexity Analysis
Step 01

Brute Force — Linear Scan

Scan left to right to find the first occurrence, then scan right to left (or continue forward) to find the last.

Simple Linear Search

int first = -1, last = -1;
for (int i = 0; i < nums.length; i++) {
    if (nums[i] == target) {
        if (first == -1) first = i;
        last = i;
    }
}
return new int[]{first, last};

This runs in O(n) time. If every element is the target, we touch all n elements. The problem requires O(log n), so we need binary search.

💡

Key observation: A standard binary search finds any occurrence of the target. But we need the first and last specifically. The trick is to run binary search twice, with a subtle difference in how we handle equality.

Step 02

The Core Insight — Two Binary Searches Diverge on Equality

Both searches look identical except for one critical line — what happens when nums[mid] == target.

The Key Difference

FIND FIRST (Lower Bound)
when nums[mid] == target:
result = mid
hi = mid - 1
Record this position, but keep searching left — there might be an earlier occurrence.
FIND LAST (Upper Bound)
when nums[mid] == target:
result = mid
lo = mid + 1
Record this position, but keep searching right — there might be a later occurrence.

Same structure, opposite directions. When we find a match, a normal binary search would stop. But we "continue past" the match — leftward for findFirst, rightward for findLast — to ensure we reach the boundary. The result variable remembers the best match found so far.

Step 03

Walkthrough — Both Searches on [5,7,7,8,8,10]

Target = 8. Expected result: [3, 4].

The Array

5
7
7
8
8
10
0
1
2
3
4
5

findFirst — Searching for Leftmost 8

Iterlohimidnums[mid]Actionresult
105277 < 8 → lo = 3-1
235488 == 8 → result=4, hi = 34
333388 == 8 → result=3, hi = 23
32lo > hi → done3

findLast — Searching for Rightmost 8

Iterlohimidnums[mid]Actionresult
105277 < 8 → lo = 3-1
235488 == 8 → result=4, lo = 54
35551010 > 8 → hi = 44
54lo > hi → done4
🎯

Notice the divergence: Both searches hit 8 at index 4 in their second iteration. But findFirst moves hi left to keep searching, while findLast moves lo right. This is the only difference, but it changes the final result from index 3 to index 4.

Step 04

Edge Cases

Cases to Watch For

Target not present: [5,7,7,8,8,10], target=6
Both searches end with result = -1. Return [-1, -1].
All elements are target: [8,8,8,8], target=8
findFirst keeps moving left until lo > hi, landing on index 0. findLast keeps moving right, landing on index 3. Return [0, 3].
Single element [8], target=8
Both searches find index 0. Return [0, 0].
Empty array [], target=0
The while loop never executes. Return [-1, -1].
Target appears once: [1,2,3,4,5], target=3
Both searches converge on index 2. Return [2, 2].
Step 05

Full Annotated Code

class Solution {
    public int[] searchRange(int[] nums, int target) {
        return new int[]{ findFirst(nums, target), findLast(nums, target) };
    }

    // Find the LEFTMOST occurrence of target
    private int findFirst(int[] nums, int target) {
        int lo = 0, hi = nums.length - 1, result = -1;
        while (lo <= hi) {
            int mid = lo + (hi - lo) / 2;
            if (nums[mid] == target) {
                result = mid;      // record this match
                hi = mid - 1;      // but keep searching LEFT
            } else if (nums[mid] < target) {
                lo = mid + 1;
            } else {
                hi = mid - 1;
            }
        }
        return result;
    }

    // Find the RIGHTMOST occurrence of target
    private int findLast(int[] nums, int target) {
        int lo = 0, hi = nums.length - 1, result = -1;
        while (lo <= hi) {
            int mid = lo + (hi - lo) / 2;
            if (nums[mid] == target) {
                result = mid;      // record this match
                lo = mid + 1;      // but keep searching RIGHT
            } else if (nums[mid] < target) {
                lo = mid + 1;
            } else {
                hi = mid - 1;
            }
        }
        return result;
    }
}
Step 06

Interactive Demo

Enter a sorted array and target. Watch both binary searches run side by side, showing how they diverge when they encounter the target value.

Two-Panel Binary Search Visualizer



findFirst (Lower Bound)

findLast (Upper Bound)

Step 07

Complexity Analysis

Time
O(log n)
Space
O(1)

We run binary search twice -- once for the leftmost occurrence and once for the rightmost. Each binary search takes O(log n), so the total is 2 × O(log n) = O(log n). No extra data structures are used -- just a few pointer variables, giving O(1) space.

Approach Breakdown

BRUTE FORCE
O(n) time
O(1) space

A single pass from left to right records the first and last index where the element equals the target. If every element matches, the loop visits all n entries -- O(n). Only two index variables (first and last) are stored.

OPTIMAL
O(log n) time
O(1) space

Two independent binary searches each halve the search space per iteration. findFirst continues left past matches (hi = mid - 1), while findLast continues right (lo = mid + 1). Each runs in O(log n), and 2 * O(log n) = O(log n). Only a few pointer variables are needed -- no auxiliary structures.

🎯

Same binary search template with different boundary conditions gives left and right edges. When nums[mid] == target, findFirst moves hi = mid - 1 to keep searching left, while findLast moves lo = mid + 1 to keep searching right. That single-line difference is all it takes.

Coach Notes

Common Mistakes

Review these before coding to avoid predictable interview regressions.

Boundary update without `+1` / `-1`

Wrong move: Setting `lo = mid` or `hi = mid` can stall and create an infinite loop.

Usually fails on: Two-element ranges never converge.

Fix: Use `lo = mid + 1` or `hi = mid - 1` where appropriate.