First Bad Version

Problem

Given n versions [1, 2, ..., n] and an API isBadVersion(version) that returns whether a version is bad, return the first bad version such that all versions after it are bad.

  • 1 ≤ n ≤ 2³¹ - 1
  • The isBadVersion API is monotonic: if version x is bad, then all versions > x are bad.

Example

Input: n = 5
Output: 4

Versions 1, 2, and 3 are good, while versions 4 and 5 are bad. The algorithm uses binary search to efficiently narrow down the first bad version. Starting with left=1 and right=5, it checks mid=3. Since version 3 is good, it moves left to 4. Then it checks mid=4, which is bad, so it moves right to 4. When left equals right, it returns 4 as the first bad version.

Approach

Straightforward Solution

A naive approach would check each version sequentially from 1 to n, calling isBadVersion until the first bad version is found. This approach is O(n) and inefficient for large n.

Core Observation

The problem reduces to finding the boundary point in a monotonic boolean sequence where values switch from False (good) to True (bad). This boundary search is a classic use case for binary search.

Path to Optimal

Recognizing the monotonic property of isBadVersion, binary search can be applied to find the transition point efficiently. By repeatedly checking the middle version, the search space halves each iteration. If mid is bad, the first bad version lies at mid or to the left; if mid is good, it lies to the right. This logic narrows down the search to O(log n) time.

Optimal Approach

Initialize two pointers, left = 1 and right = n. While left < right, compute mid = (left + right) // 2. If isBadVersion(mid) is True, move right to mid; else move left to mid + 1. When left equals right, return left as the first bad version.

Time

O(log n)

Each iteration halves the search space by moving either left or right pointer, resulting in logarithmic time relative to n.

Space

O(1)

Only a fixed number of variables (left, right, mid) are used, regardless of input size.

Pattern Spotlight

Modified Binary Search (Boundary Search)

When searching for a boundary or transition point in a monotonic predicate over a sorted domain, use binary search to repeatedly halve the search space by testing the midpoint and adjusting boundaries accordingly.

Solution

1class Solution:
2 def firstBadVersion(self, n: int) -> int:
3
4 left = 1
5 right = n
6
7 while left < right:
8
9 mid = (left + right) // 2
10
11 if isBadVersion(mid):
12 right = mid
13 else:
14 left = mid + 1
15
16 return left

Step-by-Step Solution

1

Initialize Search Boundaries to Cover All Versions

4left = 1
5right = n

Objective

To set the initial search range covering all versions from 1 to n.

Key Insight

Starting with left at 1 and right at n ensures the entire version range is considered. This setup is essential because the first bad version could be anywhere in this range, and the binary search will progressively narrow it down.

Interview Quick-Check

Core Logic

Setting left = 1 and right = n initializes the binary search boundaries to the full version range.

State & Boundaries

The search space is inclusive of both left and right pointers, ensuring no versions are skipped.

2

Iteratively Narrow Search Space Using Binary Search

7while left < right:
9 mid = (left + right) // 2
11 if isBadVersion(mid):
12 right = mid
13 else:
14 left = mid + 1

Objective

To repeatedly halve the search space by checking the middle version and adjusting boundaries based on whether it is bad.

Key Insight

By calculating mid as the integer midpoint of left and right, the algorithm tests whether the first bad version lies to the left or right. If mid is bad, the first bad version is at mid or earlier, so right is moved to mid. Otherwise, left is moved to mid + 1. This logic exploits the monotonicity of isBadVersion to discard half the search space each iteration.

Interview Quick-Check

Core Logic

The binary search condition uses isBadVersion(mid) to decide which half of the search space to keep, ensuring the first bad version is never excluded.

State & Boundaries

The loop continues while left < right, stopping when the search space is reduced to a single element.

Common Pitfalls & Bugs

Calculating mid as (left + right) // 2 avoids overflow in languages with fixed integer sizes and ensures correct midpoint.

3

Return the Identified First Bad Version

16return left

Objective

To return the converged pointer as the first bad version after the search space is narrowed to one element.

Key Insight

When left equals right, the search space cannot be reduced further, and this position corresponds to the first bad version. Returning left at this point provides the correct answer.

Interview Quick-Check

Core Logic

Returning left after the loop terminates yields the earliest version that is bad.

Line Analysis

This solution has 4 Critical lines interviewers watch for.

Line 16 Critical
return left

Return the converged pointer as the first bad version.

When left equals right, the search space is a single version, which must be the first bad version due to the binary search narrowing.

Line 11 Critical
if isBadVersion(mid):

Check if the midpoint version is bad using the API.

This check determines which half of the search space contains the first bad version, leveraging the monotonic property of isBadVersion.

Line 12 Critical
right = mid

Move the right pointer to mid when the midpoint version is bad.

If mid is bad, the first bad version could be mid itself or an earlier version. Moving right to mid keeps mid inside the search space while eliminating later versions.

Line 14 Critical
left = mid + 1

Move the left pointer to mid + 1 to search later versions.

If mid is not bad, then mid and all earlier versions are confirmed good. Moving left to mid + 1 discards those versions and continues the search in the later part of the range.

Line 13 Core
else:

Conclude that the search must continue in the right half of the range.

If mid is not bad, this confirms that the first bad version must occur strictly after mid because all earlier versions are guaranteed to be good.

Line 7 Core
while left < right:

Continue binary search while the search interval has more than one candidate.

The loop continues while the search interval contains more than one candidate. Each iteration shrinks the range while maintaining the invariant that the first bad version lies within [left, right].

Line 9 Core
mid = (left + right) // 2

Calculate the midpoint of the current search range.

Computing mid as the integer division of (left + right) // 2 splits the search space evenly, enabling efficient halving each iteration.

Line 4 Standard
left = 1

Initialize the left pointer to the first version.

Starting at 1 ensures the search covers all versions from the beginning, as versions are numbered starting at 1.

Line 5 Standard
right = n

Initialize the right pointer to the last version.

Setting right to n covers the entire range of versions, ensuring no potential first bad version is excluded.

Test Your Understanding

Why does moving the right pointer to mid when isBadVersion(mid) is True guarantee that the first bad version is not missed?

Because if mid is bad, the first bad version must be at mid or before it, so narrowing the search to [left, mid] preserves the first bad version within the search space.

Related Problems

Binary Search pattern

Don't just read it. Drill it.

Reconstruct First Bad Version from memory until it sticks. AlgoDrill blanks out key lines and makes you fill them back in, step by step.

Unlock the First Bad Version drill

or drill a free problem