Hook: Counting characters looks tiny, but interviewers love it because it checks whether you can turn a simple loop into a clean HashMap solution.
Question: Character frequency using HashMap.
Answer: Loop through the string one character at a time, store each character as a key in a HashMap<Character, Integer>, and increase its value every time you see it again. The first time a character appears, its count is 1; after that, you add 1 to the existing count. This is a classic one-pass counting problem with average O(n) time and O(k) extra space, where k is the number of distinct characters.
Interview-Ready Answer: I would create a HashMap<Character, Integer>, scan the string once, and for each character either set the count to 1 or increment the existing count. That gives me average O(n) time and O(k) space. If I need stable output order, I would switch to LinkedHashMap; and if the input can contain emoji or other supplementary Unicode characters, I would count code points instead of raw char values.
A frequency map is just a tally board: every time you see a character, you add one to its counter. In Java, HashMap is a good fit because it stores a key and a value, and it gives you fast average-time lookup and update.
null or empty. In interview code, returning an empty map is often clean and safe; in stricter APIs, you may throw an exception instead.Map<Character, Integer> frequency = new HashMap<>();. The key is the character, and the value is how many times you have seen it.1. If it already exists, fetch the old count and write back old + 1. Java 8 also gives you merge(ch, 1, Integer::sum), which does the same job more compactly.equals(). For frequency counting, this is normal and still fast on average.16 and the default load factor is 0.75. That means a resize is triggered around 12 stored entries in the default map. Resizing is expensive for that moment, but the cost is amortized over many operations, so the average still behaves like O(1) per update.Use this pattern when you need to count how often something appears and the exact order does not matter. It is one of the most common building blocks in coding interviews because it shows that you understand maps, loops, and incremental updates. If the problem asks for the most frequent character, unique characters, anagrams, or word counts, the same idea applies.
| Approach | Best for | Time | Notes |
|---|---|---|---|
| HashMap | General case | O(n) | Flexible, unordered |
| Array | ASCII only | O(n) | Fastest, fixed size |
| TreeMap | Sorted keys | O(n log k) | Always ordered |
| LinkedHashMap | Need order | O(n) | Keeps insertion order |
Array vs HashMap: If the input is guaranteed to be small ASCII, an array like int[128] is simpler and uses less overhead. But a HashMap works for a wider range of characters and is usually the safer interview answer unless the problem narrows the alphabet.
Time complexity is average O(n) because each character causes one lookup and one update. Space complexity is O(k), where k is the number of distinct characters, not the length of the whole string. If the string is a million characters long but uses only 26 letters, the map stays small.
A practical optimization is pre-sizing when you know the approximate number of distinct characters. For example, if you expect around 100 unique keys, a larger initial capacity can reduce resizing. In normal interview code, though, the default constructor is perfectly fine unless the input is huge.
One subtle gotcha is Unicode. In Java, char is a UTF-16 code unit, not always a full user-visible character. That means emoji and some rare symbols can be split into two char values. If the prompt says to count real characters, use string.codePoints() instead of iterating over chars.
Another gotcha is ordering. A plain HashMap does not promise insertion order or sorted order, so its printed order may look random. If you need predictable order, choose the right map type instead of hoping HashMap behaves nicely by accident.
Real-World Story: Imagine a chat app that scans messages for spam patterns like repeated letters, repeated punctuation, or copied text. A moderation service counts character frequencies to spot messages like heyyyyyy!!! and to build simple anti-spam features. The logic is fast, easy to deploy, and cheap to run on every message.
Now picture a bug: the team counted characters with raw char iteration, but users started sending emoji-heavy messages. Because emoji are represented by surrogate pairs in UTF-16, the service counted them as two separate units instead of one real character. The symptom was a spike in false spam flags, weird counts in logs, and support tickets saying normal messages were being delayed or rejected. The fix was to switch the counting logic to Unicode code points for any place where full character accuracy mattered.
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
public class CharacterFrequencyDemo {
// Counts UTF-16 char values. This is correct for simple text, but not for every Unicode symbol.
public static Map<Character, Integer> countCharacters(String input) {
Map<Character, Integer> frequency = new HashMap<>();
// A safe API choice: return an empty map for null or empty input instead of crashing.
if (input == null || input.isEmpty()) {
return frequency;
}
for (char ch : input.toCharArray()) {
// One lookup, one update. This is the core HashMap frequency pattern.
frequency.put(ch, frequency.getOrDefault(ch, 0) + 1);
}
return frequency;
}
// Counts Unicode code points, which is safer when emoji or rare symbols matter.
public static Map<Integer, Integer> countCodePoints(String input) {
Map<Integer, Integer> frequency = new HashMap<>();
if (input == null || input.isEmpty()) {
return frequency;
}
input.codePoints().forEach(cp ->
frequency.put(cp, frequency.getOrDefault(cp, 0) + 1)
);
return frequency;
}
// TreeMap is only for stable printing in this demo; the counting itself uses HashMap.
private static <K extends Comparable<K>> Map<K, Integer> sortedCopy(Map<K, Integer> map) {
return new TreeMap<>(map);
}
public static void main(String[] args) {
String normal = "banana";
String empty = "";
String nullInput = null;
String emoji = "a😊a";
System.out.println("Input: " + normal);
System.out.println("char counts: " + sortedCopy(countCharacters(normal)));
System.out.println();
System.out.println("Input: empty string");
System.out.println("char counts: " + sortedCopy(countCharacters(empty)));
System.out.println();
System.out.println("Input: null");
System.out.println("char counts: " + sortedCopy(countCharacters(nullInput)));
System.out.println("code point counts: " + sortedCopy(countCodePoints(nullInput)));
System.out.println();
System.out.println("Input: " + emoji);
System.out.println("char counts (UTF-16 units): " + sortedCopy(countCharacters(emoji)));
System.out.println("code point counts (real Unicode characters): " + sortedCopy(countCodePoints(emoji)));
}
}TreeMap if you want keys sorted automatically, or count in a HashMap first and sort the entries afterward. The counting stays simple; only the output behavior changes.getOrDefault and merge? getOrDefault does a manual read-then-put, while merge expresses the update more directly as “combine old value and new value.” Both are valid; merge(ch, 1, Integer::sum) is often cleaner.Map<String, Integer> instead of Map<Character, Integer>. The same counting idea still applies.HashMap preserve insertion order? No. A plain HashMap does not guarantee order, so if order matters you should use LinkedHashMap for insertion order or TreeMap for sorted order.char the same as a real character? Not always. A char is a UTF-16 code unit, so some characters, especially emoji, need two char values. If the problem means full Unicode characters, use code points.null input? It depends on the API contract. In interview code, returning an empty map is a reasonable, safe choice; in production, you should follow the method’s expected behavior consistently.Common Mistakes:
merge.HashMap does not keep insertion order, so do not rely on printed order unless you choose the right map type.char for every Unicode case. This breaks emoji and other supplementary characters. If the problem cares about real Unicode characters, count code points.Memory Hook: Picture each character walking into a store and getting a stamp on a tally card. HashMap is the cashier’s board: every visit adds one more stamp, so you never recount from scratch.
Cheat Sheet:
Map<Character, Integer> for standard frequency counting.O(n); extra space is O(k).HashMap is unordered; use LinkedHashMap or TreeMap if order matters.chars.Practice Tasks:
"mississippi" and print the result.