Implement binary search from scratch
Company: Uber
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: Medium
Interview Round: Onsite
Quick Answer: An Uber software engineer onsite coding question asking you to implement binary search on a sorted integer array from scratch, in both iterative and recursive form. It covers plain search, first/last occurrence with duplicates, the insertion-index variant, overflow-safe midpoint computation, loop invariants and correctness, and time/space complexity analysis.
Binary Search — Basic Search
Constraints
- 0 <= len(nums) <= 10^5
- nums is sorted in non-decreasing order
- -10^9 <= nums[i], target <= 10^9
- Do not use any built-in search helpers (bisect, Arrays.binarySearch, etc.)
Examples
Input: ([1, 3, 5, 6], 5)
Expected Output: 2
Explanation: 5 sits at index 2.
Input: ([1, 3, 5, 6], 2)
Expected Output: -1
Explanation: 2 is absent; return -1.
Input: ([], 7)
Expected Output: -1
Explanation: Empty array — nothing to find.
Input: ([4], 4)
Expected Output: 0
Explanation: Single-element match at index 0.
Input: ([4], 9)
Expected Output: -1
Explanation: Single-element no match.
Input: ([-5, -3, -1, 0, 2], -3)
Expected Output: 1
Explanation: Works with negatives; -3 is at index 1.
Input: ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 1)
Expected Output: 0
Explanation: First element boundary.
Input: ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 10)
Expected Output: 9
Explanation: Last element boundary.
Hints
- Maintain an inclusive search window [lo, hi]. Invariant: if target exists, its index is within [lo, hi].
- Compute the midpoint as lo + (hi - lo) // 2 to avoid integer overflow in fixed-width languages.
- When nums[mid] < target, the answer (if any) is strictly to the right, so set lo = mid + 1; otherwise move hi = mid - 1. The loop ends when lo > hi (empty window).
Binary Search — First Occurrence (Lower Bound)
Constraints
- 0 <= len(nums) <= 10^5
- nums is sorted in non-decreasing order and may contain duplicates
- -10^9 <= nums[i], target <= 10^9
- Do not use any built-in search helpers
Examples
Input: ([1, 2, 2, 2, 5, 7], 2)
Expected Output: 1
Explanation: First of the three 2s is at index 1.
Input: ([1, 2, 2, 2, 5, 7], 5)
Expected Output: 4
Explanation: Unique value 5 at index 4.
Input: ([1, 2, 2, 2, 5, 7], 4)
Expected Output: -1
Explanation: 4 is absent.
Input: ([], 3)
Expected Output: -1
Explanation: Empty array.
Input: ([5, 5, 5, 5], 5)
Expected Output: 0
Explanation: All duplicates — first is index 0.
Input: ([7], 7)
Expected Output: 0
Explanation: Single-element match.
Input: ([-3, -3, -1, 0], -3)
Expected Output: 0
Explanation: Leftmost negative duplicate at index 0.
Input: ([1, 1, 2, 3, 3, 3], 3)
Expected Output: 3
Explanation: First 3 is at index 3.
Hints
- A plain binary search returns the first match it lands on, which may be a middle duplicate, not the leftmost one.
- On nums[mid] == target, store mid as the best answer so far, then move hi = mid - 1 to keep probing the left half.
- The 'equal' and 'greater' branches both move hi left, so you can merge them: if nums[mid] >= target, go left; else go right — recording the index only on exact equality.
Binary Search — Last Occurrence (Upper Bound)
Constraints
- 0 <= len(nums) <= 10^5
- nums is sorted in non-decreasing order and may contain duplicates
- -10^9 <= nums[i], target <= 10^9
- Do not use any built-in search helpers
Examples
Input: ([1, 2, 2, 2, 5, 7], 2)
Expected Output: 3
Explanation: Last of the three 2s is at index 3.
Input: ([1, 2, 2, 2, 5, 7], 5)
Expected Output: 4
Explanation: Unique value 5 at index 4.
Input: ([1, 2, 2, 2, 5, 7], 4)
Expected Output: -1
Explanation: 4 is absent.
Input: ([], 3)
Expected Output: -1
Explanation: Empty array.
Input: ([5, 5, 5, 5], 5)
Expected Output: 3
Explanation: All duplicates — last is index 3.
Input: ([7], 7)
Expected Output: 0
Explanation: Single-element match.
Input: ([-3, -3, -1, 0], -3)
Expected Output: 1
Explanation: Rightmost -3 is at index 1.
Input: ([1, 1, 2, 3, 3, 3], 1)
Expected Output: 1
Explanation: Last 1 is at index 1.
Hints
- Symmetric to first-occurrence: record the match, then continue into the right half instead of the left.
- On nums[mid] == target, store mid and set lo = mid + 1. The 'equal' and 'less' branches both move lo right, so they can be merged.
- Because lo only ever increases and hi only ever decreases by at least one each iteration, the window strictly shrinks and the loop terminates when lo > hi.
Binary Search — Insertion Index (Search Insert Position)
Constraints
- 0 <= len(nums) <= 10^5
- nums is sorted in non-decreasing order
- -10^9 <= nums[i], target <= 10^9
- Do not use any built-in search helpers
Examples
Input: ([1, 3, 5, 6], 5)
Expected Output: 2
Explanation: 5 is present at index 2.
Input: ([1, 3, 5, 6], 2)
Expected Output: 1
Explanation: 2 would be inserted at index 1, between 1 and 3.
Input: ([1, 3, 5, 6], 7)
Expected Output: 4
Explanation: 7 is larger than all, appended at index 4.
Input: ([1, 3, 5, 6], 0)
Expected Output: 0
Explanation: 0 is smaller than all, inserted at index 0.
Input: ([1, 3, 5, 6], 4)
Expected Output: 2
Explanation: 4 would be inserted at index 2.
Input: ([], 5)
Expected Output: 0
Explanation: Empty array — insert at index 0.
Input: ([2, 2, 2], 2)
Expected Output: 0
Explanation: Lower bound of all-duplicates is index 0.
Input: ([-5, -2, 0, 3], -3)
Expected Output: 1
Explanation: -3 inserts between -5 and -2 at index 1.
Hints
- Use a half-open window [lo, hi) with hi initialized to len(nums), not len(nums) - 1. Invariant: every index < lo has value < target, every index >= hi has value >= target.
- When nums[mid] < target, move lo = mid + 1; otherwise keep mid as a candidate with hi = mid (do NOT use mid - 1 here).
- The loop ends when lo == hi; that common value is the lower bound — the first position whose value is >= target, i.e. the insertion point (and the first-occurrence index if target is present).