What Is an Anagram? The Coding Interview String Pattern, Explained

- Anagram detection reduces to building a frequency profile and comparing it — LeetCode 242, 49, and 438 are the same mechanism in three different containers
- Sort both strings for readability (O(n log n), O(n) space); use a 26-slot count array for ASCII input (O(n), O(1) space); switch to a hash map for Unicode
- LeetCode 49 (Group Anagrams) uses the sorted string or a 26-element frequency tuple as a hash key to bucket all anagrams together
- LeetCode 438 (Find All Anagrams) is a sliding window problem; the most common bug is leaving zero-count keys in the window Counter, which breaks the equality check
- Always state the O(1) space assumption out loud before writing the count-array solution — interviewers will ask about Unicode if you don't mention it first
- Strings of different lengths can never be anagrams; the early length check is optional for correctness but saves you from sorting two mismatched inputs
Two strings walk into a coding interview. One is "listen", the other is "silent". Different letters, different vibes, seemingly nothing in common. Then the interviewer asks if they're anagrams and suddenly you're questioning your entire career.
An anagram is a word or phrase formed by rearranging all the letters of another, using each letter exactly once. In a coding interview, the question is always the same: do these two strings contain the exact same characters in the exact same quantities? The insight takes five seconds. The embarrassing edge case you forgot takes another fifteen minutes.
Two Bags of Letters, One Correct Answer
Think of each string as a bag of letters. "anagram" and "nagaram" are the same bag: three a's, one n, one g, one r, one m. The order you pull letters out doesn't matter. What matters is that both bags contain identical contents.
This bag metaphor is the whole concept. Two strings are anagrams if and only if their letter inventories match.
The edge cases follow directly from the definition. Strings of different lengths can never be anagrams because one bag would have leftover letters. Case sensitivity matters: "Listen" and "silent" are not anagrams unless you normalize both to lowercase first. Spaces and punctuation: check with your interviewer, but most LeetCode problems restrict input to lowercase ASCII and spare you that particular rabbit hole.
Sorting Works. It's Just Not the Answer They Want.
If two bags contain the same letters, sorting both strings produces identical results. "anagram" sorted becomes "aaagmnr". "nagaram" sorted also becomes "aaagmnr". Equal strings mean equal bags. Done.
def is_anagram(s: str, t: str) -> bool: if len(s) != len(t): return False return sorted(s) == sorted(t)
function isAnagram(s: string, t: string): boolean { if (s.length !== t.length) return false; return s.split("").sort().join("") === t.split("").sort().join(""); }
Sort is your most readable option and works for any character set. Time complexity is O(n log n) where n is the length of the strings. Space is O(n) because sorting creates new arrays in most languages. The length check is optional for correctness but saves you from sorting two obviously unequal strings, which feels bad even when it doesn't matter.
Your interviewer will accept this. They will also, politely, ask if you can do better. That's the cue.
The O(n) Flex That Actually Gets You the Hire
Instead of sorting, count how many times each character appears in both strings and compare the counts. For lowercase ASCII letters, you can use a fixed array of 26 slots. No allocations. No sorting. Just arithmetic.
The key insight is that the array index doubles as the character identity: ord(c) - ord('a') maps 'a' to 0, 'b' to 1, and so on up to 'z' at 25.
def is_anagram(s: str, t: str) -> bool: if len(s) != len(t): return False counts = [0] * 26 for c in s: counts[ord(c) - ord('a')] += 1 for c in t: counts[ord(c) - ord('a')] -= 1 return all(x == 0 for x in counts)
function isAnagram(s: string, t: string): boolean { if (s.length !== t.length) return false; const counts = new Array(26).fill(0); for (const c of s) counts[c.charCodeAt(0) - 97]++; for (const c of t) counts[c.charCodeAt(0) - 97]--; return counts.every(x => x === 0); }
Increment while scanning s, decrement while scanning t. If every slot returns to zero, the inventories match. Time is O(n). Space is O(1) because the array is always 26 slots regardless of input length.
That O(1) claim only holds for a bounded character set. Which brings us to the trap.
If you want to understand why hash maps give O(1) average lookup, see the breakdown at /blog/hash-map-time-complexity.
The Unicode Question That Will End Your Interview
The 26-slot array breaks the moment your input includes Unicode, Chinese characters, or arbitrary symbols. A prepared candidate says "Can I assume lowercase ASCII, or is the input Unicode?" An unprepared candidate ships the 26-slot solution for a string containing "café" and gets a very educational runtime error.
Always ask: "Can I assume lowercase ASCII, or is the input Unicode?" This costs you zero time and signals that you understand the hidden assumption buried in your O(1) space claim. Interviewers write this down. In a good way.
from collections import Counter def is_anagram(s: str, t: str) -> bool: return Counter(s) == Counter(t)
Counter builds a frequency map for each string. Comparing two Counter objects is O(k) where k is the number of distinct characters. Space is O(k) for the same reason. The length check is implicit: if the total character counts differ, the Counters won't be equal.
For TypeScript, use a Map<string, number>. Build one map from s, decrement with t, verify all values are zero. Same idea, more ceremony.
Three Problems. One Mechanism. Zero Excuses.
LeetCode 242: Valid Anagram
The direct application of everything above. Use the counting approach, mention you'd switch to a hash map for Unicode. This is a warm-up problem. The interviewer is watching whether you state the O(1) space assumption before you write the code. Don't make them ask.
LeetCode 49: Group Anagrams
Given a list of strings, group all anagrams together. The trick is finding a canonical key that every anagram in a group shares.
The readable key is the sorted string: "eat", "tea", and "ate" all sort to "aet". Hash map it.
from collections import defaultdict def group_anagrams(strs: list[str]) -> list[list[str]]: groups = defaultdict(list) for s in strs: groups[tuple(sorted(s))].append(s) return list(groups.values())
Time complexity is O(n * k log k) where n is the number of strings and k is the maximum string length. If you replace tuple(sorted(s)) with a 26-element frequency tuple, the sort becomes O(k) and total time drops to O(n * k). Faster, but harder to explain out loud, and explaining it out loud is the whole interview.
LeetCode 438: Find All Anagrams in a String
Given strings s and p, return all start indices where a substring of length len(p) is an anagram of p. This is a sliding window problem. See the full pattern at /blog/sliding-window-algorithm.
from collections import Counter def find_anagrams(s: str, p: str) -> list[int]: if len(p) > len(s): return [] p_count = Counter(p) window = Counter(s[:len(p)]) result = [] if window == p_count: result.append(0) for i in range(len(p), len(s)): incoming = s[i] outgoing = s[i - len(p)] window[incoming] += 1 window[outgoing] -= 1 if window[outgoing] == 0: del window[outgoing] if window == p_count: result.append(i - len(p) + 1) return result
You must delete keys with a count of zero. If you leave zero-count keys in the window Counter, window == p_count returns False even when the anagram matches, because the two Counters have different key sets. This is the most common bug on this problem. Everyone forgets it exactly once. Usually during an interview, with the interviewer watching.
Time complexity is O(n + m) where n is the length of s and m is the length of p. Space is O(k) for the two frequency maps.
Two Bugs That Will Bite You
The zero-key deletion issue from LeetCode 438 is patient. It waits until the worst possible moment, then strikes. When you slide the window, check if the outgoing character's count dropped to zero and delete it before comparing. Every time. No exceptions.
State your O(1) space assumption explicitly before you write the code, not after. If your interviewer is paying attention, they'll ask what happens with Unicode. Don't make them ask. That follow-up question is the interviewer writing "missed hidden assumption" in their notes.
Complexity at a Glance
| Approach | Time | Space | When to Use |
|---|---|---|---|
| Sort both strings | O(n log n) | O(n) | Simple check, any character set |
| Count array (26 slots) | O(n) | O(1) | Lowercase ASCII only |
| Hash map / Counter | O(n) | O(k) | Unicode or unknown character set |
| Sliding window (438) | O(n + m) | O(k) | Substring anagram search |
Every Anagram Problem Is the Same Problem
Once you see it, you can't unsee it. Every anagram problem reduces to building a frequency profile and then comparing, combining, or sliding that profile.
LeetCode 242 compares two profiles directly. LeetCode 49 uses a profile as a hash key to group strings. LeetCode 438 maintains a sliding profile and checks it against a fixed target. The mechanism is identical. The container changes.
Anagram problems show up as warm-ups because they test three things at once: recognizing the pattern, picking the right data structure, and stating complexity assumptions out loud. That last part is a separate skill from solving problems on a keyboard. If you want to practice explaining your reasoning under actual interview conditions, SpaceComplexity runs voice-based mock interviews with rubric feedback across all four dimensions interviewers actually score.
For the string fundamentals that sit underneath anagram problems, see /blog/string-coding-interview.