Back to Stream Practice: Collections & Numbers
Q9Question 9 of 11

Find the second-largest number in a list using streams

Key Concepts
  • distinct().sorted(reverseOrder()).skip(1).findFirst() returns the second-largest distinct value
  • distinct() ensures the second-largest is genuinely different from the largest
  • Without distinct, a duplicated maximum would be reported as the second-largest
  • Returns Optional.empty() when fewer than two distinct values exist
  • For O(n) performance on large lists, track top two values in a single pass with reduce
  • Confirm with the business whether duplicates count toward second-largest

Definition

distinct().sorted(Comparator.reverseOrder()).skip(1).findFirst() returns the second-largest distinct number as an Optional.
  • 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?

JAVA
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"));
    }
}
TEXT
Second-largest: Rs.90000

Why is distinct() critical?

JAVA
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?

JAVA
// 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 |

JAVA
// 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?

JAVA
// 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.

JAVA
// 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.

JAVA
// 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

DIAGRAM
  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.

---

Share
Use ← → arrow keys to navigate