Back to Stream Practice: Collections & Numbers
Q10Question 10 of 11

Find the second-smallest number in a list using streams

Key Concepts
  • distinct().sorted().skip(1).findFirst() gives the second-smallest distinct number
  • sorted() without arguments uses natural ascending order, placing the smallest first
  • distinct() prevents a duplicated minimum from being reported as the second-smallest
  • Mirrors the second-largest pattern exactly, just with ascending instead of descending order
  • Returns Optional.empty() when fewer than two distinct values exist
  • For large lists, a two-variable O(n) pass tracking smallest and second-smallest is more efficient

Definition

distinct().sorted().skip(1).findFirst() returns the second-smallest distinct number by sorting ascending and skipping the minimum.
  • distinct() collapses duplicate values so the second-smallest is genuinely different from the smallest.
  • sorted() uses natural ascending order, placing the smallest first.
  • skip(1) drops the minimum.
  • findFirst() returns the second-smallest as an Optional.
  • Mirrors the second-largest pattern with ascending instead of descending order.

Priya at the National Bank of India removes duplicate fee amounts, lines them up from smallest to largest, ignores the lowest, and picks the next one to understand the low end of the fee range.

Understanding the Concepts

What does this question ask?

The interviewer wants the second-smallest value from a list. This mirrors the second-largest question but with ascending order.

How do you find the second-smallest number?

JAVA
package com.bank.service;

import java.util.*;

public class SecondSmallest {
    public static void main(String[] args) {
        List<Integer> fees = List.of(
            500, 1000, 250, 500, 750, 250);

        Optional<Integer> secondSmallest = fees.stream()
            .distinct()
            .sorted()
            .skip(1)
            .findFirst();

        System.out.println("Second-smallest fee: Rs."
            + secondSmallest.map(String::valueOf).orElse("N/A"));
    }
}
TEXT
Second-smallest fee: Rs.500

How is this different from the second-largest?

| Aspect | Second-largest | Second-smallest |
|---|---|---|
| Sort order | reverseOrder() (descending) | naturalOrder() (ascending) |
| Skips | Maximum value | Minimum value |
| Returns | Runner-up from top | Runner-up from bottom |

The pattern is identical; only the sort direction changes.

Why does distinct matter here?

JAVA
List<Integer> fees = List.of(250, 250, 500);

// WRONG: without distinct, second element is still 250
Optional<Integer> wrong = fees.stream()
    .sorted().skip(1).findFirst();
System.out.println("Without distinct: " + wrong.get());  // 250!

// CORRECT: distinct collapses duplicate amounts
Optional<Integer> correct = fees.stream()
    .distinct().sorted().skip(1).findFirst();
System.out.println("With distinct: " + correct.get());  // 500

What about negative numbers?

JAVA
List<Integer> mixed = List.of(-10, 5, -10, 20, -5);

Optional<Integer> result = mixed.stream()
    .distinct()
    .sorted()
    .skip(1)
    .findFirst();

System.out.println(result.get());  // -5

Natural ordering handles negative numbers correctly: -10 < -5 < 5 < 20.

What are the edge cases?

JAVA
// All same: no second-smallest
Optional<Integer> same = List.of(100, 100).stream()
    .distinct().sorted().skip(1).findFirst();
System.out.println(same.isPresent());  // false

// Single element: no second
Optional<Integer> single = List.of(100).stream()
    .distinct().sorted().skip(1).findFirst();
System.out.println(single.isPresent());  // false

// Empty list
Optional<Integer> empty = List.<Integer>of().stream()
    .distinct().sorted().skip(1).findFirst();
System.out.println(empty.isPresent());  // false

What about the O(n) alternative?

JAVA
int min = Integer.MAX_VALUE;
int secondMin = Integer.MAX_VALUE;
for (int n : fees) {
    if (n < min) {
        secondMin = min;
        min = n;
    } else if (n < secondMin && n != min) {
        secondMin = n;
    }
}

This single-pass approach is O(n), while the stream sort is O(n log n).

What are common mistakes?

Mistake: using sorted(reverseOrder()) when you want the second-smallest.

JAVA
// WRONG: descending gives second-largest, not second-smallest
Optional<Integer> wrong = fees.stream()
    .distinct()
    .sorted(Comparator.reverseOrder())
    .skip(1)
    .findFirst();  // second-LARGEST

// CORRECT: ascending (default) for second-smallest
Optional<Integer> correct = fees.stream()
    .distinct()
    .sorted()  // ascending
    .skip(1)
    .findFirst();

What are common interview follow-up questions?

  • "How do you find both second-smallest and second-largest?" Combine both patterns or use a single sort with skip from both ends.
  • "How do you find the Nth-smallest?" Change skip(1) to skip(n-1).
  • "How do you find the median?" Sort, then take the middle element(s).

Visual Overview

DIAGRAM
  List: [500, 1000, 250, 500, 750, 250]
       │
       │  .distinct()
       ▼
  [500, 1000, 250, 750]
       │
       │  .sorted()  (ascending)
       ▼
  [250, 500, 750, 1000]
       │
       │  .skip(1)
       ▼
  [500, 750, 1000]
       │
       │  .findFirst()
       ▼
  Optional[500]  ← second-smallest

Key Takeaway

Finding the second-smallest mirrors the second-largest, just in ascending order. Call distinct so the result is genuinely the second distinct value rather than a repeat of the minimum, sort with natural ascending order, skip the first element which is the smallest, and take findFirst.

Distinct is the semantic detail: for a list like 250, 250, 500, without it the second-smallest would wrongly come back as 250. Natural ordering works correctly even with negative numbers. For large inputs, a single-pass two-variable approach runs in O(n) instead of O(n log n).

At the National Bank of India, Priya used this in a fee analysis to understand the low end of the fee range by finding the second-smallest distinct fee amount.

Remember one sentence: distinct-sorted-skip(1)-findFirst gives the second-smallest distinct value, with ascending order being the only difference from the second-largest pattern.

---

Share
Use ← → arrow keys to navigate