Definition
- Classifier
s -> s.charAt(0)extracts the first character. - Empty strings cause
StringIndexOutOfBoundsException; filter them out before grouping. - Case-insensitive grouping normalizes with
Character.toUpperCase(s.charAt(0)). - TreeMap gives alphabetical key order for display.
- Total function: the classifier must return a valid key for every element.
Priya at the National Bank of India files product codes by their first character: all the S-codes (SAV, SBI) together, all the H-codes (HDFC) together, creating an alphabetical directory for quick lookups.
Understanding the Concepts
What does this question ask?
The interviewer wants you to group strings by their initial character. This tests groupingBy with a custom classifier, null/empty safety, and case handling.
How do you group strings by first letter?
package com.bank.core;
import java.util.*;
import java.util.stream.Collectors;
public class GroupByFirstLetter {
public static void main(String[] args) {
List<String> codes = List.of(
"SAV2001", "SBI", "HDFC", "ICICI", "SAV2002", "FD2024");
Map<Character, List<String>> byInitial = codes.stream()
.collect(Collectors.groupingBy(
s -> s.charAt(0),
TreeMap::new,
Collectors.toList()));
byInitial.forEach((ch, list) ->
System.out.println(ch + ": " + list));
}
}
F: [FD2024]
H: [HDFC]
I: [ICICI]
S: [SAV2001, SBI, SAV2002]
How do you handle case-insensitive grouping?
Map<Character, List<String>> caseInsensitive = codes.stream()
.collect(Collectors.groupingBy(
s -> Character.toUpperCase(s.charAt(0)),
TreeMap::new,
Collectors.toList()));
This ensures "savings" and "SAV2001" both land under 'S'.
What happens with empty strings?
// WRONG: charAt(0) on empty string throws exception
List<String> withEmpty = Arrays.asList("SAV", "", "HDFC");
Map<Character, List<String>> wrong = withEmpty.stream()
.collect(Collectors.groupingBy(s -> s.charAt(0)));
// StringIndexOutOfBoundsException!
// CORRECT: filter out null and empty strings
Map<Character, List<String>> safe = withEmpty.stream()
.filter(s -> s != null && !s.isEmpty())
.collect(Collectors.groupingBy(s -> s.charAt(0)));
How do you count strings per initial?
Map<Character, Long> countByInitial = codes.stream()
.collect(Collectors.groupingBy(
s -> s.charAt(0),
TreeMap::new,
Collectors.counting()));
countByInitial.forEach((ch, count) ->
System.out.println(ch + ": " + count + " codes"));
F: 1 codes
H: 1 codes
I: 1 codes
S: 3 codes
How is this different from grouping by length?
| Aspect | Group by length | Group by first letter | |---|---|---| | Classifier | String::length | s -> s.charAt(0) | | Key type | Integer | Character | | Empty string risk | Returns 0 (safe) | Throws exception (unsafe) | | Use case | Size validation | Alphabetical directory |
The groupingBy mechanism is identical; only the classifier function differs.
What are common mistakes?
Mistake: not filtering nulls when the list can contain null elements.
// WRONG: NPE on null.charAt(0)
List<String> withNull = Arrays.asList("SAV", null);
withNull.stream()
.collect(Collectors.groupingBy(s -> s.charAt(0))); // NPE!
// CORRECT: filter nulls
withNull.stream()
.filter(Objects::nonNull)
.filter(s -> !s.isEmpty())
.collect(Collectors.groupingBy(s -> s.charAt(0)));
What about multi-level grouping by first two characters?
Map<String, List<String>> byPrefix = codes.stream()
.filter(s -> s.length() >= 2)
.collect(Collectors.groupingBy(
s -> s.substring(0, 2),
TreeMap::new,
Collectors.toList()));
What are common interview follow-up questions?
- "How do you power alphabetical jump navigation?" Use this grouping to create an index from letter to codes.
- "How do you sort the strings within each group?" Use collectingAndThen to sort each list.
- "How do you find the initial with the most codes?" Stream the entrySet and max by list size.
Visual Overview
List: ["SAV2001", "SBI", "HDFC", "ICICI", "SAV2002", "FD2024"]
│
│ groupingBy(s -> s.charAt(0), TreeMap)
▼
┌──────────────────────────────────────────┐
│ F → ["FD2024"] │
│ H → ["HDFC"] │
│ I → ["ICICI"] │
│ S → ["SAV2001", "SBI", "SAV2002"] │
└──────────────────────────────────────────┘
Keys in alphabetical order (TreeMap)
Key Takeaway
Grouping strings by their first letter uses groupingBy with a classifier that extracts charAt(0). The result is a Map from initial character to the list of matching strings, which is the basis of alphabetical directories and quick-jump navigation.
The critical robustness detail is that charAt(0) throws on an empty string, so null and empty strings must be filtered before grouping. Normalize with toUpperCase for case-insensitive grouping, and supply a TreeMap for alphabetical key order.
At the National Bank of India, Rajesh used this to power alphabetical navigation in a product-code browser, letting users jump directly to all S-codes or all H-codes.
Remember one sentence: groupingBy(s -> s.charAt(0)) creates an alphabetical directory, but filter empty strings first because charAt(0) on an empty string throws.
---