Find earliest supporting dependency version
Company: OpenAI
Role: Machine Learning Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Technical Screen
##### Question
Given a list of dependency versions (e.g. [103.003.02, 103.003.03, 203.003.02]) and a black-box API isSupported(v), design an algorithm to find the earliest (lowest) version that supports a target feature.
2) Versions follow {major}.{minor}.{patch}. Support is not monotonic: a higher version may drop support, but it is guaranteed that some later version will support again. The API is rate-limited, so total calls must be sub-linear to the number of versions. Devise a strategy—e.g., group by latest patch per major, binary-search majors, then minors, then patches—to minimize API usage while reliably returning the earliest supporting version.
Quick Answer: This question evaluates a candidate's ability to design efficient search algorithms and reason about algorithmic complexity under constraints such as non-monotonic predicate behavior and a rate-limited black-box API.
Find the **earliest dependency version** that supports a target feature, using as few API probes as possible.
## What to implement
```
solution(versions, supported_versions)
```
- **`versions`** — a list of distinct version strings, each in the form `"major.minor.patch"` (numeric components, possibly zero-padded, e.g. `"104.000.01"`). The list is already sorted in ascending semantic-version order.
- **`supported_versions`** — a list that simulates a black-box API `isSupported(v)`: a version supports the feature **if and only if** it appears in `supported_versions`. This list is always a subset of `versions`.
Return the **earliest** (smallest in the sorted order) version string that supports the feature, or **`None`** if no version supports it.
## Why this isn't a plain search
Support is **not monotonic** across the full version list: an early version may support the feature, a later version (in a new major or minor) may not, and an even later one may support again. So you cannot simply scan or binary-search the whole list directly.
## Structure guarantees (these make a sub-linear API strategy possible)
The data obeys a three-level hierarchical pattern:
1. **Major level.** If any version supports the feature, there is a **first supporting major**, and every major after it also contains at least one supporting version.
2. **Minor level.** Inside that first supporting major, there is a **first supporting minor**, and every later minor within that major also contains at least one supporting version.
3. **Patch level.** Inside that first supporting major/minor pair, support is **monotonic by patch** among the listed versions — once support starts, it continues.
Additionally, **if a major or a minor contains any supporting version, then its latest listed version is guaranteed to be supporting.** This lets you probe a single representative version per major and per minor.
The earliest supporting version is therefore the first supporting patch inside the first supporting minor inside the first supporting major.
## Constraints
- `0 <= len(versions) <= 200000`
- `versions` is sorted ascending, all versions are distinct, and `supported_versions` is a subset of `versions`.
- The support pattern always obeys the hierarchical guarantees above: if support exists at all, a first supporting major exists; within it a first supporting minor exists; within it a first supporting patch exists; and the latest listed version of any supporting major or minor is also supporting.
## Examples
- `versions = ["103.003.01", "103.003.02", "103.004.01", "104.000.01", "104.001.01", "104.001.02", "105.000.01"]`, `supported_versions = ["103.004.01", "104.001.02", "105.000.01"]` → `"103.004.01"`
- `versions = ["101.000.01", "101.001.01", "102.000.01", "102.000.02", "102.001.01", "103.000.01"]`, `supported_versions = ["102.000.02", "102.001.01", "103.000.01"]` → `"102.000.02"`
- `versions = ["200.000.01"]`, `supported_versions = ["200.000.01"]` → `"200.000.01"`
- `versions = ["300.000.01", "300.000.02"]`, `supported_versions = []` → `None`
- `versions = []`, `supported_versions = []` → `None`
Constraints
- 0 <= len(versions) <= 200000
- versions is sorted in ascending semantic-version order, all versions are distinct, and supported_versions is a subset of versions
- The support pattern obeys the hierarchical guarantees described above: a first supporting major exists if support exists at all; within it, a first supporting minor exists; within it, a first supporting patch exists; and the latest listed version of any supporting major or minor is also supporting
Examples
Input: (["103.003.01", "103.003.02", "103.004.01", "104.000.01", "104.001.01", "104.001.02", "105.000.01"], ["103.004.01", "104.001.02", "105.000.01"])
Expected Output: "103.004.01"
Explanation: Major 103 is the first major whose latest version supports the feature. Inside it, minor 004 is the first supporting minor, and its only listed patch is the answer.
Input: (["101.000.01", "101.001.01", "102.000.01", "102.000.02", "102.001.01", "103.000.01"], ["102.000.02", "102.001.01", "103.000.01"])
Expected Output: "102.000.02"
Explanation: No version in major 101 supports the feature. In major 102, the first supporting minor is 000, and within that minor the first supporting patch is 02.
Input: (["200.000.01"], ["200.000.01"])
Expected Output: "200.000.01"
Explanation: The only version is supported, so it is also the earliest supporting version.
Input: (["300.000.01", "300.000.02"], [])
Expected Output: None
Explanation: No version supports the feature, so the correct return value is None.
Input: ([], [])
Expected Output: None
Explanation: An empty version list contains no supporting version.
Hints
- You do not need to probe every version. The latest version inside a major tells you whether that major contains any supporting version.
- After finding the first supporting major, repeat the same idea for minors, then binary search patches inside the chosen minor.