Back to Stream Practice: Collections & Numbers
Q3Question 3 of 11

Find the second-largest number from a list using streams

Key Concepts
  • distinct().sorted(reverseOrder()).skip(1).findFirst() gives the second-largest distinct value
  • distinct() is critical: without it, a repeated max value makes skip(1) return another copy of the max
  • Returns Optional.empty() if fewer than 2 distinct values exist
  • The pattern generalizes to Nth-largest with skip(n-1)
  • For Employee salary, map to BigDecimal first then apply the same pattern
  • The sorted approach is O(n log n); for top-2 only, a two-pass max approach is O(n)

Definition

Use distinct() to remove duplicate values, sorted(reverseOrder()) for descending order, skip(1) to pass the largest, and findFirst() to get the second-largest.
  • distinct() removes duplicate values so repeated maximums do not count as "second largest."
  • sorted(reverseOrder()) orders values from highest to lowest.
  • skip(1) passes over the single largest value.
  • findFirst() returns the second-largest as an Optional.
  • Without distinct(), if the largest value appears twice, skip(1) returns another copy of the largest instead of the actual second-largest.
  • Returns Optional.empty() if fewer than 2 distinct values exist in the list.

At National Bank of India, the risk desk tracks daily trade volumes: [500, 300, 500, 200, 400]. To find the second-highest volume, first remove duplicates (500 appears twice), sort descending [500, 400, 300, 200], skip the first (500), and the answer is 400.

Understanding the Concepts

How do you find the second-largest number?

JAVA
package com.bank.analytics;

import java.util.*;

public class SecondLargestDemo {
    public static void main(String[] args) {
        List<Integer> volumes = List.of(500, 300, 500, 200, 400);

        Optional<Integer> secondLargest = volumes.stream()
            .distinct()
            .sorted(Comparator.reverseOrder())
            .skip(1)
            .findFirst();

        System.out.println("Second largest: " + secondLargest.orElse(-1));
    }
}
TEXT
Second largest: 400

Why is distinct() critical?

JAVA
// WRONG: without distinct, repeated max skews the result
List<Integer> data = List.of(100, 100, 50);

Optional<Integer> wrong = data.stream()
    .sorted(Comparator.reverseOrder())  // [100, 100, 50]
    .skip(1)                            // skips one 100
    .findFirst();                       // returns 100 again!

Optional<Integer> correct = data.stream()
    .distinct()                          // [100, 50]
    .sorted(Comparator.reverseOrder())   // [100, 50]
    .skip(1)                             // skips 100
    .findFirst();                        // returns 50 ✓

What if there is only one distinct value?

JAVA
List<Integer> allSame = List.of(100, 100, 100);

Optional<Integer> result = allSame.stream()
    .distinct()                          // [100]
    .sorted(Comparator.reverseOrder())   // [100]
    .skip(1)                             // empty
    .findFirst();                        // Optional.empty()

System.out.println(result.orElse(-1));   // -1

How do you generalize to Nth-largest?

JAVA
// Generic Nth-largest
int n = 3;
Optional<Integer> nthLargest = volumes.stream()
    .distinct()
    .sorted(Comparator.reverseOrder())
    .skip(n - 1)
    .findFirst();

What is a more efficient O(n) alternative?

The sorted approach is O(n log n). For just the second-largest, a two-variable scan is O(n).

JAVA
// O(n) approach: track first and second largest
int first = Integer.MIN_VALUE;
int second = Integer.MIN_VALUE;
for (int v : volumes) {
    if (v > first) {
        second = first;
        first = v;
    } else if (v > second && v != first) {
        second = v;
    }
}
// first=500, second=400

The stream approach is cleaner and more readable. The imperative approach is more efficient for very large datasets.

What about BigDecimal for salary?

JAVA
// Second-highest salary (BigDecimal)
import java.math.BigDecimal;

List<BigDecimal> salaries = List.of(
    new BigDecimal("1800000"), new BigDecimal("1000000"),
    new BigDecimal("1500000"), new BigDecimal("1000000"));

Optional<BigDecimal> secondHighest = salaries.stream()
    .distinct()
    .sorted(Comparator.reverseOrder())
    .skip(1)
    .findFirst();
// Rs.1500000

What should a senior engineer know?

  • distinct() relies on equals() and hashCode(). For BigDecimal, be aware that new BigDecimal("10.0").equals(new BigDecimal("10.00")) is false because scale differs. Use compareTo equality if needed.
  • The sorted().skip(1).findFirst() pipeline on a finite stream does not short-circuit the sort: all elements must be sorted before skip can proceed.
  • For finding top-K elements efficiently, consider a TreeSet with limited size or a priority queue rather than full sorting.

Visual Overview

DIAGRAM
  [500, 300, 500, 200, 400]
       │
       ▼
  distinct()
  [500, 300, 200, 400]
       │
       ▼
  sorted(reverseOrder())
  [500, 400, 300, 200]
       │
       ├── skip(0).findFirst() → 500  (largest)
       ├── skip(1).findFirst() → 400  (2nd largest)
       └── skip(2).findFirst() → 300  (3rd largest)

Key Takeaway

Second-largest uses distinct().sorted(reverseOrder()).skip(1).findFirst(). The distinct() is critical: without it, if the max value appears twice, skip(1) returns another copy of the max, not the actual second-largest distinct value. The result is Optional because there might not be two distinct values.

The pattern generalizes to Nth-largest with skip(n-1). For production use on very large datasets, the O(n) two-variable approach is more efficient than the O(n log n) sort, but the stream approach is cleaner for interview-scale data.

At National Bank of India, the risk desk finds the second-highest daily trade volume (400) from a list that contains a duplicated maximum (500 appears twice). Without distinct, the answer would incorrectly be 500 again.

Remember one sentence: distinct before sorted before skip is the pattern, and forgetting distinct is the interview trap.

---

Share
Use ← → arrow keys to navigate