Definition
- **string.chars().mapToObj(c -> (char) c)** produces a `Stream<Character>`. - **groupingBy(c -> c, counting())** creates a `Map<Character, Long>` of frequencies. - **max(Map.Entry.comparingByValue())** finds the entry with the highest count. - Returns **Optional** to handle the empty-string case. - For ties, max returns one arbitrary winner; extend the comparator for determinism.
Priya at the National Bank of India tallies how often each letter appears in a reference code and picks the letter with the biggest tally.
Understanding the Concepts
How do you find the most frequent character?
Count characters, then take the max by count.
package com.bank.core;
import java.util.*;
import java.util.stream.Collectors;
public class MostFrequentChar {
public static void main(String[] args) {
String input = "national bank";
Optional<Character> mostFrequent = input.chars()
.mapToObj(c -> (char) c)
.collect(Collectors.groupingBy(
c -> c, Collectors.counting()))
.entrySet().stream()
.max(Map.Entry.comparingByValue())
.map(Map.Entry::getKey);
mostFrequent.ifPresent(ch ->
System.out.println("Most frequent: '" + ch + "'"));
}
}
Most frequent: 'a'
'a' appears 3 times in 'national bank' (positions 1, 5, 10).
How do you exclude whitespace?
Optional<Character> noSpace = input.chars()
.mapToObj(c -> (char) c)
.filter(c -> !Character.isWhitespace(c))
.collect(Collectors.groupingBy(c -> c, Collectors.counting()))
.entrySet().stream()
.max(Map.Entry.comparingByValue())
.map(Map.Entry::getKey);
How do you handle case-insensitive comparison?
Optional<Character> caseInsensitive = input.toLowerCase().chars()
.mapToObj(c -> (char) c)
.filter(c -> !Character.isWhitespace(c))
.collect(Collectors.groupingBy(c -> c, Collectors.counting()))
.entrySet().stream()
.max(Map.Entry.comparingByValue())
.map(Map.Entry::getKey);
How do you handle ties deterministically?
If two characters share the top count, max returns an arbitrary one. Extend the comparator.
// Deterministic: on tie, pick the character that appears first alphabetically
Optional<Character> deterministic = freqMap.entrySet().stream()
.max(Map.Entry.<Character, Long>comparingByValue()
.thenComparing(Map.Entry.comparingByKey()))
.map(Map.Entry::getKey);
What about an empty string?
chars() on an empty string produces an empty IntStream, so the frequency map is empty and max returns Optional.empty().
String empty = "";
// Result: Optional.empty()
What are common mistakes?
Mistake: not deciding whether whitespace and case matter.
// Without filtering whitespace, ' ' might be the most frequent character
String s = "a a a b b";
// Most frequent without filter: ' ' (space, 4 occurrences)
// Most frequent with filter: 'a' (3 occurrences)
What should a senior engineer know?
- The two-pass approach (count then max) is O(n) and the clearest to read.
- For very large inputs, folding the max tracking into the counting pass avoids building an intermediate map, but for typical strings the two-pass version is preferred.
- chars() handles BMP characters. For emoji or supplementary characters, use codePoints().
- This is a common interview warm-up question that tests stream, groupingBy, and comparator skills.
What are common interview follow-up questions?
- "How do you find the least frequent character?" Use min instead of max.
- "How do you find all characters with the maximum frequency?" Find the max count, then filter all entries matching it.
- "How do you find the top 3 most frequent characters?" Sort descending by value and limit(3).
Visual Overview
String: "national bank"
│
│ chars() → mapToObj
▼
Stream<Character> ['n','a','t','i','o','n','a','l',' ','b','a','n','k']
│
│ groupingBy(c, counting())
▼
Map<Character, Long>:
n → 3, a → 3, t → 1, i → 1, o → 1,
l → 1, ' ' → 1, b → 1, k → 1
│
│ max(comparingByValue())
▼
Optional[a → 3] (or n → 3, depending on tie-breaking)
│ .map(getKey)
▼
Optional['a']
Key Takeaway
To find the most frequent character, count each character with groupingBy and counting, then take the max by value. The result is an Optional containing the character with the highest count.
Decide up front whether whitespace counts and whether the comparison is case-sensitive; both change the answer. For ties, extend the comparator with thenComparing for deterministic results.
At the National Bank of India, this pattern was used in a diagnostics tool to spot padding characters in account references. The algorithm is O(n) with two passes: one to count, one to find the max.
Remember one sentence: groupingBy(c, counting()) then max(comparingByValue()) finds the most frequent character in O(n), and always decide about whitespace and case first.
---