Count Character Frequency in a String
Fast recall
Quick revision
Core answer
charAt() and update a Map using merge or getOrDefault. For actual Unicode code points, use s.codePoints() and a Map. An int[26] is simpler only when the input contract is exactly lowercase English a through z.Whether you can use HashMap for frequency counting — this pattern appears in dozens of string and array problems.
Using a nested loop (O(n²)) to count each character. The HashMap approach is O(n).
Mention the int[26] array approach for lowercase-only strings — O(1) space by using char - 'a' as an index. Also mention that this exact pattern is used in anagram checking: two strings are anagrams if their frequency maps are equal.
Interview-ready guide
Interview answer
I'd build a frequency map in a single pass. Take a HashMap, walk the string once with charAt, and for each character do freq.put(c, freq.getOrDefault(c, 0) + 1). The getOrDefault is what makes it clean — it hands back 0 the first time it sees a character, so there's no null check and no special first-time branch. That's O(n) time and O(k) space, where k is the number of distinct characters.
The mistake I'd explicitly avoid is the nested loop — picking a character and rescanning the whole string to count it. That's O(n²) and there's no reason for it once you have the map.
If the interviewer says the input is only lowercase letters, I'd switch to an int[26] and index with c - 'a'. That drops it to O(1) fixed space, skips boxing char into Character, and runs faster — but I'd call out that it assumes ASCII lowercase, so anything with uppercase, digits, or Unicode goes out of bounds and I'd go back to the map. For edge cases I'd clarify whether case and spaces count and normalize with toLowerCase(Locale.ROOT) for locale-neutral technical matching first. The reason this question shows up so often is the pattern itself — the same frequency map is how you check anagrams and find the first non-repeating character.
Count Character Frequency in a String
Direct Answer
For UTF-16 code-unit counts, scan with charAt() and update a Map<Character, Integer> using merge or getOrDefault. For actual Unicode code points, use s.codePoints() and a Map<Integer, Integer>. An int[26] is simpler only when the input contract is exactly lowercase English a through z.
Code Example
import java.util.HashMap;
import java.util.Map;
public class CharFrequency {
public static void main(String[] args) {
// Approach 1: HashMap — works for any characters
String s = "programming";
Map<Character, Integer> freq = new HashMap<>();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
freq.put(c, freq.getOrDefault(c, 0) + 1);
}
System.out.println(freq);
// {p=1, r=2, o=1, g=2, a=1, m=2, i=1, n=1}
// Approach 2: int[26] — faster for lowercase-only strings
int[] count = new int[26];
for (char c : s.toCharArray()) {
count[c - 'a']++; // 'a'=0, 'b'=1, ..., 'z'=25
}
for (int i = 0; i < 26; i++) {
if (count[i] > 0) {
System.out.println((char)('a' + i) + ": " + count[i]);
}
}
// a: 1, g: 2, i: 1, m: 2, n: 1, o: 1, p: 1, r: 2
// Anagram check — two strings are anagrams if their frequency maps are equal
System.out.println(isAnagram("listen", "silent")); // true
}
static boolean isAnagram(String a, String b) {
if (a.length() != b.length()) return false;
int[] count = new int[26];
for (char c : a.toCharArray()) count[c - 'a']++;
for (char c : b.toCharArray()) count[c - 'a']--;
for (int n : count) if (n != 0) return false;
return true;
}
}
Common Mistakes & Pitfalls
- Using a nested loop — picking each character and rescanning the whole string to count it is O(n²); a single pass with a
HashMap(orint[26]) is O(n) and a better scalable answer. - Assuming ASCII with
int[26]—count[c - 'a']silently goes out of bounds or collides the moment the input has uppercase, digits, spaces, or Unicode. Useint[26]only when the input is guaranteed lowercasea–z; otherwise choose a Character map or a code-point map according to the requirement. - Forgetting
getOrDefault—freq.put(c, freq.get(c) + 1)throws aNullPointerExceptionthe first time it sees a character becausegetreturnsnull, which unboxes to nothing. UsegetOrDefault(c, 0)ormerge(c, 1, Integer::sum). - Not deciding how case and whitespace count — treating
'A'and'a'as the same, or ignoring spaces, is a requirements question; clarify and normalize withtoLowerCase(Locale.ROOT)for locale-neutral technical matching up front rather than assuming.
Check your understanding
Answer these without looking back at the article.
- How do you find the first non-repeating character in a String?
- How do you check if two Strings are anagrams?
- What is the time complexity of building a frequency map?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.