Definition
- distinct() removes duplicates so the second-largest is genuinely a different value.
- sorted(reverseOrder()) orders values descending.
- skip(1) drops the maximum value.
- findFirst() takes the next value, which is the second-largest, as an Optional.
- Optional.empty() is returned when fewer than two distinct values exist.
Priya at the National Bank of India removes duplicate transaction amounts, lines them up from biggest to smallest, ignores the top one, and picks the runner-up for an anomaly check.
Understanding the Concepts
What does this question ask?
The interviewer wants the second-largest value from a list. This tests distinct, sorted with reverse order, skip, findFirst, and whether you understand the semantics of 'second-largest'.
How do you find the second-largest number?
package com.bank.service;
import java.util.*;
public class SecondLargest {
public static void main(String[] args) {
List<Integer> amounts = List.of(
50000, 125000, 75000, 125000, 90000, 50000);
Optional<Integer> secondLargest = amounts.stream()
.distinct()
.sorted(Comparator.reverseOrder())
.skip(1)
.findFirst();
System.out.println("Second-largest: Rs."
+ secondLargest.map(String::valueOf).orElse("N/A"));
}
}
Second-largest: Rs.90000
Why is distinct() critical?
List<Integer> amounts = List.of(125000, 125000, 90000);
// WRONG: without distinct, second element is still 125000
Optional<Integer> wrong = amounts.stream()
.sorted(Comparator.reverseOrder())
.skip(1)
.findFirst();
System.out.println("Without distinct: " + wrong.get()); // 125000!
// CORRECT: distinct collapses duplicate amounts
Optional<Integer> correct = amounts.stream()
.distinct()
.sorted(Comparator.reverseOrder())
.skip(1)
.findFirst();
System.out.println("With distinct: " + correct.get()); // 90000
How do you handle edge cases?
// Fewer than 2 distinct values: empty Optional
Optional<Integer> oneValue = List.of(100, 100, 100).stream()
.distinct().sorted(Comparator.reverseOrder())
.skip(1).findFirst();
System.out.println(oneValue.isPresent()); // false
// Empty list: empty Optional
Optional<Integer> empty = List.<Integer>of().stream()
.distinct().sorted(Comparator.reverseOrder())
.skip(1).findFirst();
System.out.println(empty.isPresent()); // false
// Two distinct values: returns the smaller one
Optional<Integer> two = List.of(100, 200).stream()
.distinct().sorted(Comparator.reverseOrder())
.skip(1).findFirst();
System.out.println(two.get()); // 100
How is this different from a single-pass O(n) approach?
| Approach | Time | Space | Clarity |
|---|---|---|---|
| distinct-sort-skip | O(n log n) | O(n) for sort buffer | High |
| Two-variable single pass | O(n) | O(1) | Medium |
// O(n) single-pass approach
int max = Integer.MIN_VALUE;
int secondMax = Integer.MIN_VALUE;
for (int n : amounts) {
if (n > max) {
secondMax = max;
max = n;
} else if (n > secondMax && n != max) {
secondMax = n;
}
}
Use the stream approach for readability on moderate data; switch to the loop for performance-critical paths.
What about using max twice?
// Alternative: find max, then find max of the rest
Optional<Integer> maxVal = amounts.stream().max(Comparator.naturalOrder());
maxVal.flatMap(max -> amounts.stream()
.filter(n -> n < max)
.max(Comparator.naturalOrder()))
.ifPresent(second ->
System.out.println("Second: Rs." + second));
This also works and avoids sorting, but makes two passes.
What are common mistakes?
Mistake: using skip(1) without distinct.
// WRONG: skip(1) skips the first element, not the first distinct value
// If max appears twice, second element is still max
// CORRECT: always use distinct() before sort-and-skip
Mistake: not handling the Optional when fewer than 2 values exist.
// WRONG: get() on empty Optional throws NoSuchElementException
int wrong = amounts.stream().distinct()
.sorted(Comparator.reverseOrder())
.skip(1).findFirst().get(); // may throw!
// CORRECT: use orElse or ifPresent
int correct = amounts.stream().distinct()
.sorted(Comparator.reverseOrder())
.skip(1).findFirst().orElse(-1);
What are common interview follow-up questions?
- "How do you find the Nth-largest?" Change skip(1) to skip(n-1).
- "How do you find the second-largest per department?" Group first, then apply the pattern within each group.
- "What if duplicates should count?" Remove distinct() so repeated values occupy separate positions.
Visual Overview
List: [50000, 125000, 75000, 125000, 90000, 50000]
│
│ .distinct()
▼
[50000, 125000, 75000, 90000]
│
│ .sorted(reverseOrder())
▼
[125000, 90000, 75000, 50000]
│
│ .skip(1)
▼
[90000, 75000, 50000]
│
│ .findFirst()
▼
Optional[90000] ← second-largest
Key Takeaway
Finding the second-largest number uses distinct to collapse duplicate values, sorted with reverse order to arrange descending, skip(1) to drop the maximum, and findFirst to take the runner-up. The distinct step ensures the second-largest is genuinely a different value from the largest.
Without distinct, a list like 125000, 125000, 90000 would wrongly report 125000 as the second-largest. The Optional honestly represents lists with fewer than two distinct values by returning empty.
At the National Bank of India, Priya used this in a transaction analysis to report the second-largest distinct amount in a batch for an anomaly check. For large datasets, a single-pass two-variable approach runs in O(n) instead of O(n log n) for the sort.
Remember one sentence: distinct-sort(reverseOrder)-skip(1)-findFirst gives the second-largest distinct value, and without distinct a duplicated max would be wrongly reported.
---