Back to Stream: Grouping & Partitioning
Q18Question 18 of 22

Group a list of strings by their length and count each group into Map<Integer, Long>

Key Concepts
  • groupingBy(String::length, counting()) produces Map<Integer, Long> of length to count
  • counting() never materializes the group members, it only increments a counter per bucket
  • counting() returns Long, not Integer, to avoid overflow on very large groups
  • This is a length-distribution histogram useful for spotting anomalies in batch data
  • Supply TreeMap::new for sorted histogram output
  • Swap the downstream to summingInt, averagingDouble, or others for different aggregations over the same grouping

Definition

groupingBy(String::length, counting()) buckets strings by length and counts the members of each bucket, producing a Map<Integer, Long> histogram.
  • Two-argument groupingBy takes a classifier and a downstream collector.
  • counting() counts elements in each group without storing them, returning Long.
  • More efficient than grouping into lists and taking each list's size.
  • Histogram output shows the distribution of string lengths.
  • TreeMap supplier sorts length keys for clean presentation.

Priya at the National Bank of India sorts reference codes into length bins, but instead of keeping the codes she writes down how many are in each bin: three codes of length 3, one code of length 7, and so on.

Understanding the Concepts

What does this question ask?

The interviewer wants a count of strings at each length. This tests the two-argument groupingBy with counting() as the downstream collector.

How do you create a length histogram?

JAVA
package com.bank.core;

import java.util.*;
import java.util.stream.Collectors;

public class LengthHistogram {
    public static void main(String[] args) {
        List<String> codes = List.of(
            "SAV", "CUR", "SAV2001", "FD2024", "HDFC", "ICICI", "SBI");

        Map<Integer, Long> histogram = codes.stream()
            .collect(Collectors.groupingBy(
                String::length,
                TreeMap::new,
                Collectors.counting()));

        histogram.forEach((len, count) ->
            System.out.println("Length " + len + ": " + count + " codes"));
    }
}
TEXT
Length 3: 3 codes
Length 4: 1 codes
Length 5: 1 codes
Length 6: 1 codes
Length 7: 1 codes

Why is counting() more efficient than grouping into lists?

JAVA
// Approach 1: group into lists, then get sizes
Map<Integer, List<String>> lists = codes.stream()
    .collect(Collectors.groupingBy(String::length));
Map<Integer, Integer> sizes = new HashMap<>();
lists.forEach((k, v) -> sizes.put(k, v.size()));
// Stores every string in memory, then discards

// Approach 2: counting() directly
Map<Integer, Long> counts = codes.stream()
    .collect(Collectors.groupingBy(String::length, Collectors.counting()));
// Never stores the strings, only increments counters

counting() is more efficient because it never materializes the group members.

How do you swap the downstream for different aggregations?

DIAGRAM
| Downstream | Result type | What it computes |
|---|---|---|
| counting() | Long | Number of strings per length |
| summingInt(s -> 1) | Integer | Same as counting but as int |
| joining(", ") | String | Comma-separated strings per length |
| toSet() | Set<String> | Unique strings per length |

How do you find the most common length?

JAVA
Optional<Map.Entry<Integer, Long>> mostCommon = histogram.entrySet()
    .stream()
    .max(Map.Entry.comparingByValue());

mostCommon.ifPresent(e ->
    System.out.println("Most common length: " + e.getKey()
        + " (" + e.getValue() + " strings)"));
TEXT
Most common length: 3 (3 strings)

What about detecting anomalies?

JAVA
package com.bank.core;

import java.util.*;
import java.util.stream.Collectors;

public class LengthAnomaly {
    public static void main(String[] args) {
        List<String> codes = List.of(
            "SAV", "CUR", "SBI", "SAV2001", "XX");

        Map<Integer, Long> histogram = codes.stream()
            .collect(Collectors.groupingBy(
                String::length, TreeMap::new, Collectors.counting()));

        int expectedLength = 3;
        histogram.entrySet().stream()
            .filter(e -> e.getKey() != expectedLength)
            .forEach(e -> System.out.println(
                "ANOMALY: " + e.getValue() + " codes have length "
                + e.getKey() + " (expected " + expectedLength + ")"));
    }
}
TEXT
ANOMALY: 1 codes have length 2 (expected 3)
ANOMALY: 1 codes have length 7 (expected 3)

What are common mistakes?

Mistake: expecting Integer instead of Long from counting().

JAVA
// WRONG: counting() returns Long, not Integer
// Map<Integer, Integer> wrong = ...groupingBy(..., counting());
// compile error: incompatible types

// CORRECT: use Long
Map<Integer, Long> correct = codes.stream()
    .collect(Collectors.groupingBy(String::length, Collectors.counting()));

What about visualizing the histogram?

JAVA
histogram.forEach((len, count) -> {
    String bar = "#".repeat(count.intValue());
    System.out.printf("Length %d: %s (%d)%n", len, bar, count);
});
TEXT
Length 3: ### (3)
Length 4: # (1)
Length 5: # (1)
Length 6: # (1)
Length 7: # (1)

What are common interview follow-up questions?

DIAGRAM
- "How do you get counts as Integer instead of Long?" Use summingInt(s -> 1) instead of counting().
- "How do you filter lengths with count above a threshold?" Stream the entrySet and filter by value.
- "How do you create a character frequency histogram?" Same pattern with chars() and groupingBy(c -> c, counting()).

Visual Overview

DIAGRAM
  List: ["SAV", "CUR", "SAV2001", "FD2024", "HDFC", "ICICI", "SBI"]
       │
       │  groupingBy(String::length, counting())
       ▼
  ┌────────────────────────────┐
  │  Length 3 → 3 (SAV,CUR,SBI)│
  │  Length 4 → 1 (HDFC)       │
  │  Length 5 → 1 (ICICI)      │
  │  Length 6 → 1 (FD2024)     │
  │  Length 7 → 1 (SAV2001)    │
  └────────────────────────────┘
  Note: strings are counted, not stored

Key Takeaway

To build a length-distribution histogram, use groupingBy with String::length as the classifier and counting() as the downstream. This counts how many strings fall into each length bucket without ever storing the strings themselves, making it more efficient than grouping into lists and measuring each list.

counting() deliberately returns Long so the histogram stays correct even for very large groups. Supply a TreeMap for sorted output. At the National Bank of India, Rajesh used this in a data-quality check to detect malformed batches by spotting an unexpected spike at a particular length.

The key insight is that swapping the downstream is the general lever: counting for counts, summingInt for sums, averagingDouble for averages, all over the same grouping. The classifier stays the same.

Remember one sentence: groupingBy with counting() as the downstream builds a histogram that counts per group without storing the members.

---

Share
Use ← → arrow keys to navigate