The Runner-Up
An easy Python interview practice problem on DataDriven. Write the Python and run it against test cases, with instant feedback.
- Domain
- Python
- Difficulty
- easy
- Seniority
- L5
- Asked in
- Gemini
Problem
A leaderboard already highlights the top score, and the product team wants to show the runner-up beside it. Given a list of scores, return the second-largest distinct value, or None when no runner-up exists.
Not the winner. The one just behind it.
Example 1
Input:
nums = [10,20,30,20,10]
Output:
20
Example 2
Input:
nums = [5,5,5]
Output:
null
Worked solution and explanation
What this really is
This is a distinctness problem wearing a 'find the second biggest' costume, and the whole thing lives in one word: distinct. Anyone can find the max. Candidates stumble when they read 'second largest' as 'the second-biggest element' and forget that a tie at the top is not a runner-up. Hand them [5, 5, 5] and they confidently return 5, when the right answer is None because there is no second distinct value at all.
The trap, concretely
The tempting one-liner is sorted(nums, reverse=True)[1]. On [10, 20, 30, 20, 10] it returns 20, which is correct here by luck. Now try [30, 30, 10]: sorted descending is [30, 30, 10], index 1 is 30, and you have just declared the largest value its own runner-up. Worse, that shape can never return None. The fix is not a smarter sort, it is collapsing duplicates first so a repeated maximum only counts once.
- Naive: sort and index
- sorted([5, 5, 3], reverse=True)[1] returns 5. The duplicate max fills position 1, so the winner becomes its own runner-up, and this form never yields None.
- Correct: dedup, then rank
- sorted(set([5, 5, 3])) is [3, 5]; index -2 is 3, the real runner-up. When the set collapses to a single value, a length check returns None.
How to get there
Step 1: Collapse the duplicates
Wrap nums in a set. Every repeat of any value, including the maximum, now counts exactly once. This single step is what separates a passing answer from the buggy one.
Step 2: Rank what remains
Sort the distinct values ascending. The largest distinct value lands at index -1, and the runner-up sits right behind it at index -2. Negative values sort correctly, so nothing special is needed for scores below zero.
Step 3: Guard the None case
If fewer than two distinct values survived, there is no runner-up, so return None. This is the guard that makes all-equal inputs like [5, 5, 5] behave. Only then is it safe to index -2.
The solution
def the_runner_up(nums):
ranked = sorted(set(nums))
if len(ranked) < 2:
return None
return ranked[-2]Common follow-up questions
- How would you generalize this to the kth-largest distinct value? (Tests generalizing the dedup-and-index idea, and quickselect for O(n) average-case kth element.)
- What if you needed the index of the runner-up, not just its value? (Tests tracking indices alongside values, where the set trick no longer suffices.)
- What changes if the integers arrive as an unbounded stream you can only read once? (Tests maintaining bounded state as elements arrive one at a time, which is exactly where the single-pass two-variable form earns its keep.)