Back to Stream: Grouping & Partitioning
Q19Question 19 of 22

Group a list of strings by their first letter into Map<Character, List<String>>

Key Concepts
  • groupingBy(s -> s.charAt(0)) classifies strings by their first character into Map<Character, List<String>>
  • charAt(0) on an empty string throws StringIndexOutOfBoundsException, so filter empty strings first
  • For case-insensitive grouping, normalize with Character.toUpperCase(s.charAt(0))
  • Supply TreeMap::new to get alphabetical key order for directory-style navigation
  • The classifier must be total: every element must produce a valid key
  • This is the same groupingBy mechanism used for any derived key, just with a first-character extractor

Definition

groupingBy(s -> s.charAt(0)) groups strings by their first character, producing a Map<Character, List<String>> for alphabetical directory navigation.
  • 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?

JAVA
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));
    }
}
TEXT
F: [FD2024]
H: [HDFC]
I: [ICICI]
S: [SAV2001, SBI, SAV2002]

How do you handle case-insensitive grouping?

JAVA
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?

JAVA
// 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?

JAVA
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"));
TEXT
F: 1 codes
H: 1 codes
I: 1 codes
S: 3 codes

How is this different from grouping by length?

DIAGRAM
| 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.

JAVA
// 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?

JAVA
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

DIAGRAM
  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.

---

Share
Use ← → arrow keys to navigate