Definition
- Comparator.comparingInt(String::length) builds a comparator that orders strings by length as a primitive int.
- max() returns the element with the greatest value according to the comparator, wrapped in Optional.
- Single pass: max scans once and tracks the current maximum, which is more efficient than sorting.
- Optional.empty() is returned for an empty stream instead of null or exception.
- Tie-breaking: chain
thenComparing(naturalOrder())to choose alphabetically among ties.
Priya at the National Bank of India looks through a batch of reference codes and pulls out the one with the most characters, to check whether any code exceeds the maximum-length policy.
Understanding the Concepts
What does this question ask?
The interviewer wants you to find the longest string in a list using the Stream API. This tests max, Comparator.comparingInt, Optional handling, and tie-breaking.
How do you find the longest string?
package com.bank.core;
import java.util.*;
public class LongestString {
public static void main(String[] args) {
List<String> codes = List.of(
"SAV", "SAV2001", "HDFC-SAVINGS-2001", "CUR", "FD2024");
Optional<String> longest = codes.stream()
.max(Comparator.comparingInt(String::length));
longest.ifPresent(s ->
System.out.println("Longest: " + s + " (" + s.length() + " chars)"));
}
}
Longest: HDFC-SAVINGS-2001 (17 chars)
Why use comparingInt instead of comparing?
| Method | Extractor type | Boxing | |---|---|---| | comparingInt(String::length) | ToIntFunction | No boxing | | comparing(s -> s.length()) | Function returning Integer | Auto-boxes each length |
// Preferred: no boxing
Comparator.comparingInt(String::length)
// Works but boxes each length to Integer
Comparator.comparing(String::length)
comparingInt is slightly more efficient and communicates that the key is a primitive.
How is max different from sort-then-last?
// max: single pass O(n), clear intent
Optional<String> maxApproach = codes.stream()
.max(Comparator.comparingInt(String::length));
// sort-then-last: O(n log n), buffers all elements
String sortApproach = codes.stream()
.sorted(Comparator.comparingInt(String::length))
.reduce((first, second) -> second)
.orElse("");
max is both faster and more readable when you only need the extreme value.
How do you handle ties?
If several strings share the maximum length, max returns one arbitrary winner.
package com.bank.core;
import java.util.*;
public class LongestWithTieBreak {
public static void main(String[] args) {
List<String> codes = List.of("ABCDEF", "ZYXWVU", "MNOPQR");
// Without tie-breaking: arbitrary among length-6 strings
Optional<String> arbitrary = codes.stream()
.max(Comparator.comparingInt(String::length));
// With tie-breaking: alphabetically first among equal-length
Optional<String> deterministic = codes.stream()
.max(Comparator.comparingInt(String::length)
.thenComparing(Comparator.naturalOrder()));
System.out.println("Arbitrary: " + arbitrary.orElse(""));
System.out.println("Deterministic: " + deterministic.orElse(""));
}
}
Arbitrary: MNOPQR
Deterministic: ZYXWVU
How do you find the shortest string?
Replace max with min.
Optional<String> shortest = codes.stream()
.min(Comparator.comparingInt(String::length));
What are the edge cases?
// Empty list: returns Optional.empty
Optional<String> empty = List.<String>of().stream()
.max(Comparator.comparingInt(String::length));
System.out.println(empty.isPresent()); // false
// Single element: that element is both max and min
Optional<String> single = List.of("SAV").stream()
.max(Comparator.comparingInt(String::length));
System.out.println(single.get()); // SAV
What are common mistakes?
Mistake: not filtering null elements.
// WRONG: NPE because comparingInt calls length() on null
List<String> withNull = Arrays.asList("SAV", null, "HDFC");
Optional<String> wrong = withNull.stream()
.max(Comparator.comparingInt(String::length)); // NPE!
// CORRECT: filter nulls first
Optional<String> correct = withNull.stream()
.filter(Objects::nonNull)
.max(Comparator.comparingInt(String::length));
Mistake: using sorted().findFirst() instead of min.
// WRONG: sorts the entire stream just to find the minimum
Optional<String> expensive = codes.stream()
.sorted(Comparator.comparingInt(String::length))
.findFirst();
// CORRECT: min is a single pass
Optional<String> efficient = codes.stream()
.min(Comparator.comparingInt(String::length));
What are common interview follow-up questions?
- "How do you find all strings of maximum length?" First find the max length, then filter all strings matching it.
- "How do you find the longest string per group?" Use groupingBy with collectingAndThen(maxBy, Optional::get).
- "What is the time complexity?" O(n) for max, versus O(n log n) for sorting.
Visual Overview
List: ["SAV", "SAV2001", "HDFC-SAVINGS-2001", "CUR", "FD2024"]
│
│ max(comparingInt(String::length))
▼
┌──────────────────────────────────────────┐
│ "SAV" length 3 │
│ "SAV2001" length 7 │
│ "HDFC-SAVINGS-2001" length 17 ← max │
│ "CUR" length 3 │
│ "FD2024" length 6 │
└──────────────────────────────────────────┘
│
▼
Optional["HDFC-SAVINGS-2001"]
Key Takeaway
Finding the longest string uses stream().max() with Comparator.comparingInt(String::length). The comparingInt extractor avoids boxing, max performs a single pass tracking the current maximum, and the result is an Optional that honestly represents an empty-list edge case.
For deterministic results when multiple strings share the maximum length, chain thenComparing with naturalOrder to break ties alphabetically. Always filter out null elements before calling max, because comparingInt would call length on null and throw.
At the National Bank of India, Rajesh used this in a diagnostics tool to find the longest account reference in a batch for a maximum-length policy check.
Remember one sentence: max with comparingInt(String::length) finds the longest string in one pass, returning an Optional that is empty for an empty list.
---