First Bad Version

Mastering First Bad Version | CodingInterview.net

First Bad Version: Mastering Binary Search

Welcome back to codinginterview.net. By now, you have likely traversed arrays and manipulated pointers. Now, it is time to tackle a fundamental algorithm that every software engineer must know by heart: Binary Search. The "First Bad Version" problem is a classic gauge of whether a candidate understands how to drastically reduce search spaces. By the end of this guide, optimizing search queries and avoiding common integer overflow traps will be second nature to you.

1. Understanding the Problem

You are a product manager and currently leading a team to develop a new product. Unfortunately, the latest version of your product fails the quality check. Since each version is developed based on the previous version, all the versions after a bad version are also bad.

Suppose you have n versions [1, 2, ..., n] and you want to find out the first bad one, which causes all the following ones to be bad. You are given an API bool isBadVersion(version) which returns whether version is bad. Your goal is to implement a function to find the first bad version while minimizing the number of calls to the API.

The Core Constraint (The Trap):

API calls are expensive. If you have millions of versions, checking every single one individually will result in a "Time Limit Exceeded" error. Furthermore, when calculating the midpoint of your search space, a naive mathematical approach can cause a disastrous integer overflow in statically typed languages.

2. The Naive Approach: Linear Scan

The most intuitive way to solve this is to test each version one by one, starting from version 1. You call isBadVersion(1), then isBadVersion(2), and so on, until the API returns True.

Trade-off Analysis

While this logic guarantees you will find the first bad version, it is highly inefficient:

  • Time Complexity: O(N) — In the worst-case scenario (the very last version is the first bad one), you make N calls to the API. If N is 2 billion, your program will grind to a halt.
  • Space Complexity: O(1) — No extra memory is used.

3. The Optimal Approach: Binary Search (O(log N) Time)

To pass a senior-level technical interview, we want to achieve an O(log N) time complexity. Because the versions are sorted and the "badness" propagates (e.g., Good, Good, Bad, Bad, Bad), this is a perfect candidate for Binary Search.

Imagine looking for a word in a physical dictionary. You do not read every page from the beginning. You open it to the middle. If the word you are looking for comes alphabetically before the page you are on, you tear the book in half and throw away the right side. You repeat this process, constantly halving your search space, until you find the exact word.

We apply this exact logic to our versions. We check the middle version. If it is bad, the first bad version must be this one or somewhere to its left. If it is good, the first bad version must be to its right.

4. The Logic Step-by-Step

  1. Initialize Pointers: Set a left pointer to 1 (the first version) and a right pointer to n (the latest version).
  2. Traverse the Search Space: Create a loop that continues as long as left < right.
    • Calculate the Midpoint: Instead of doing (left + right) / 2, use left + (right - left) / 2. This mathematically yields the same result but prevents integer overflow if left and right are massive numbers close to the 32-bit integer limit.
    • The API Check: Call isBadVersion(mid).
    • If True (Bad Version): We know the current version is bad, but we don't know if it's the first one. The answer is either mid or something before it. Move the right pointer to mid.
    • If False (Good Version): We know this version is good, so everything before it is also good. The first bad version must be strictly after mid. Move the left pointer to mid + 1.
  3. When the loop breaks (left == right), the pointers have converged exactly on the first bad version. Return left.

5. Complexity Analysis

  • Time Complexity: O(log N) — The search space is halved with every iteration. For 2 billion versions, it takes a maximum of only ~31 API calls to find the exact target. A massive improvement over O(N)!
  • Space Complexity: O(1) — We only use two pointers, regardless of how large the version history is.

6. Code Implementations

Expand the sections below to see the optimal O(log N) "Binary Search" implementations across different languages.

View Python Solution
# The isBadVersion API is already defined for you.
# def isBadVersion(version: int) -> bool:

class Solution:
    def firstBadVersion(self, n: int) -> int:
        left = 1
        right = n
        
        while left < right:
            mid = left + (right - left) // 2
            
            if isBadVersion(mid):
                # The first bad version is either mid or to the left
                right = mid
            else:
                # The first bad version is strictly to the right
                left = mid + 1
                
        # left and right converge to the first bad version
        return left
View Java Solution
/* The isBadVersion API is defined in the parent class VersionControl.
      boolean isBadVersion(int version); */

public class Solution extends VersionControl {
    public int firstBadVersion(int n) {
        int left = 1;
        int right = n;
        
        while (left < right) {
            // Prevent integer overflow trap!
            int mid = left + (right - left) / 2;
            
            if (isBadVersion(mid)) {
                right = mid; // First bad is mid or before
            } else {
                left = mid + 1; // First bad is strictly after mid
            }
        }
        
        return left;
    }
}
View C++ Solution
// The API isBadVersion is defined for you.
// bool isBadVersion(int version);

class Solution {
public:
    int firstBadVersion(int n) {
        int left = 1;
        int right = n;
        
        while (left < right) {
            // Safe way to calculate midpoint without overflow
            int mid = left + (right - left) / 2;
            
            if (isBadVersion(mid)) {
                right = mid;
            } else {
                left = mid + 1;
            }
        }
        
        return left;
    }
};

7. Conclusion: You Are Ready

Congratulations, you have successfully implemented a customized Binary Search! Recognizing when a dataset has a "sorted" nature (even if it is just a sequence of booleans like False, False, True, True) is the key to unlocking O(log N) speeds. Furthermore, understanding the integer overflow trap associated with (left + right) / 2 demonstrates the kind of low-level operational awareness that top tech companies look for. Keep this Binary Search template handy, as it is the foundation for dozens of other advanced algorithms!