Definition
- comparingInt(String::length) extracts the string's length as a primitive int and sorts ascending (shortest first).
- .reversed() reverses the comparator to sort descending (longest first).
- thenComparing(Comparator.naturalOrder()) adds alphabetical ordering as a tiebreaker when two strings have the same length.
- comparingInt is preferred over
comparing(String::length)because it avoids autoboxinginttoIntegerfor each comparison. - The result is a new sorted list; the original list is not modified.
Sorting stock ticker symbols by name length at National Bank of India: "TCS" (3 letters) before "HDFC" (4 letters) before "RELIANCE" (8 letters). When two symbols have the same length, like "HDFC" and "INFY" (both 4), sort them alphabetically: HDFC before INFY.
Understanding the Concepts
What does sorting by length mean?
Sorting strings by length orders them from shortest to longest (ascending) or longest to shortest (descending). The string's content does not matter for ordering, only its character count.
How do you sort ascending by length?
package com.bank.analytics;
import java.util.*;
public class StringLengthSortDemo {
public static void main(String[] args) {
List<String> symbols = List.of(
"RELIANCE", "TCS", "HDFC", "INFY", "SBI", "WIPRO");
// Ascending by length (shortest first)
List<String> asc = symbols.stream()
.sorted(Comparator.comparingInt(String::length))
.toList();
System.out.println("Ascending: " + asc);
}
}
Ascending: [TCS, SBI, HDFC, INFY, WIPRO, RELIANCE]
How do you sort descending by length?
// Descending by length (longest first)
List<String> desc = symbols.stream()
.sorted(Comparator.comparingInt(String::length).reversed())
.toList();
System.out.println("Descending: " + desc);
Descending: [RELIANCE, WIPRO, HDFC, INFY, TCS, SBI]
How do you add a tiebreaker for equal lengths?
When two strings have the same length, their relative order is undefined without a tiebreaker. Add thenComparing(Comparator.naturalOrder()) for alphabetical tiebreak.
// Length ascending, then alphabetical for equal lengths
List<String> withTiebreak = symbols.stream()
.sorted(Comparator.comparingInt(String::length)
.thenComparing(Comparator.naturalOrder()))
.toList();
System.out.println("With tiebreak: " + withTiebreak);
With tiebreak: [SBI, TCS, HDFC, INFY, WIPRO, RELIANCE]
SBI comes before TCS (both length 3, alphabetical). HDFC comes before INFY (both length 4, alphabetical).
How is comparingInt different from comparing for length?
| Method | Autoboxing | Performance |
|--------|-----------|-------------|
| comparingInt(String::length) | No autoboxing | Faster for large lists |
| comparing(String::length) | Boxes int to Integer each comparison | Slower |
// WRONG (works but wasteful): comparing boxes int to Integer
Comparator<String> slow = Comparator.comparing(String::length);
// CORRECT (efficient): comparingInt uses primitive int
Comparator<String> fast = Comparator.comparingInt(String::length);
What are common mistakes?
WRONG: Using reversed() after thenComparing to reverse only the length.
// WRONG: reversed() reverses BOTH length and alphabetical
Comparator<String> wrong = Comparator.comparingInt(String::length)
.thenComparing(Comparator.naturalOrder())
.reversed(); // length is now DESC and alphabetical is now Z-A!
CORRECT: Reverse only the length comparator before adding the tiebreaker.
// CORRECT: length descending, alphabetical ascending
Comparator<String> correct = Comparator.comparingInt(String::length).reversed()
.thenComparing(Comparator.naturalOrder());
What about case-insensitive length sorting?
// Sort branch names by length, case-insensitive tiebreak
List<String> branches = List.of("Mumbai", "delhi", "Pune", "KOLKATA");
List<String> sorted = branches.stream()
.sorted(Comparator.comparingInt(String::length)
.thenComparing(String.CASE_INSENSITIVE_ORDER))
.toList();
// [Pune, delhi, Mumbai, KOLKATA]
What should a senior engineer know?
comparingInt,comparingLong, andcomparingDoubleavoid autoboxing and are always preferred overcomparingfor primitive fields.- The
sorted()operation is stable (TimSort), so equal-length strings maintain their original order even without an explicit tiebreaker. But relying on stability makes code fragile and non-obvious. - For very large lists,
sorted()buffers all elements in memory. Consider whether you need only the top-N shortest strings (sorted().limit(n)) which can optimize internally.
Visual Overview
["RELIANCE", "TCS", "HDFC", "INFY", "SBI", "WIPRO"]
│
▼
comparingInt(String::length)
│
┌────┴──────────────────────────────────┐
│ Length 3: TCS, SBI │
│ Length 4: HDFC, INFY │
│ Length 5: WIPRO │
│ Length 8: RELIANCE │
└────┬──────────────────────────────────┘
│
▼
thenComparing(naturalOrder())
│
┌────┴──────────────────────────────────┐
│ SBI, TCS (length 3, alpha) │
│ HDFC, INFY (length 4, alpha) │
│ WIPRO (length 5) │
│ RELIANCE (length 8) │
└───────────────────────────────────────┘
Key Takeaway
Sorting strings by length uses Comparator.comparingInt(String::length) for ascending and .reversed() for descending. The comparingInt method avoids autoboxing that comparing(String::length) would cause, which matters for large lists. For deterministic results when strings have the same length, add .thenComparing(Comparator.naturalOrder()) for alphabetical tiebreak.
At National Bank of India, the symbol autocomplete service sorts stock tickers by length so shorter symbols appear first: TCS (3), SBI (3), HDFC (4), INFY (4), WIPRO (5), RELIANCE (8). The alphabetical tiebreak ensures consistent ordering within same-length groups.
This is a common interview question that tests whether you know comparingInt for primitive extraction and thenComparing for multi-level sorting. The key trap is using .reversed() at the end of a chain with thenComparing, which reverses all keys instead of just the length.
Remember one sentence: comparingInt avoids autoboxing, and thenComparing with naturalOrder adds a deterministic tiebreaker for equal-length strings.
---