What Is a String Anagram?
An anagram is a word or string formed by rearranging all the characters of another string while keeping the same character frequencies. The order of the characters can change, but the number of occurrences of each character must remain the same.
"listen" → "silent"
"evil" → "vile"
"race" → "care"
For example, "listen" and
"silent" contain exactly the same characters:
one l, one
i, one
s, one
t, one
e, and one
n.
Anagram vs Similar-Looking Strings
Two strings are not anagrams simply because they contain similar letters. Every character must occur the same number of times.
| First String | Second String | Anagrams? |
|---|---|---|
| listen | silent | Yes |
| evil | vile | Yes |
| hello | world | No |
| aab | abb | No |
The Key Idea: Character Frequency
Anagrams are closely connected to character-frequency counting. If two strings have identical character-frequency maps, they are anagrams under the same comparison rules.
"listen"
l → 1
i → 1
s → 1
t → 1
e → 1
n → 1
"silent"
s → 1
i → 1
l → 1
e → 1
n → 1
t → 1
The order differs, but the frequencies are identical. This makes a frequency map a natural data structure for solving anagram problems.
Checking Anagrams with a Hash Map
One efficient approach is to count the characters in the first string and then subtract those counts while processing the second string. If any character is missing or its count becomes invalid, the strings are not anagrams.
function areAnagrams(first, second) {
if (first.length !== second.length) {
return false;
}
const frequency = {};
for (const char of first) {
frequency[char] = (frequency[char] || 0) + 1;
}
for (const char of second) {
if (!frequency[char]) {
return false;
}
frequency[char]--;
}
return true;
}
console.log(areAnagrams("listen", "silent"));
// true
console.log(areAnagrams("hello", "world"));
// false
Why Compare String Lengths First?
Two strings with different lengths cannot contain exactly the same characters with the same frequencies. Checking their lengths first therefore provides an immediate way to reject impossible pairs.
"listen" → length 6
"silent" → length 6
Possible anagrams
"hello" → length 5
"world" → length 5
Same length, but frequency check fails
The length check does not prove that two strings are anagrams. It is only a necessary condition. The character frequencies still need to match.
Step-by-Step Frequency Comparison
Consider "abbc" and
"cbba".
First, count the characters in the first string:
a → 1
b → 2
c → 1
Then process the second string and decrease each count:
c → 0
b → 1
b → 0
a → 0
Every count reaches zero, so the strings contain exactly the same characters and are anagrams.
Using Map for Anagram Checking
JavaScript's Map can make the frequency-table
operations explicit and is useful when building reusable map-based algorithms.
function areAnagrams(first, second) {
if (first.length !== second.length) {
return false;
}
const frequency = new Map();
for (const char of first) {
frequency.set(
char,
(frequency.get(char) || 0) + 1
);
}
for (const char of second) {
const count = frequency.get(char) || 0;
if (count === 0) {
return false;
}
frequency.set(char, count - 1);
}
return true;
}
console.log(areAnagrams("triangle", "integral"));
// true
Sorting-Based Anagram Check
Another straightforward solution is to sort the characters of both strings. If the sorted versions are identical, the original strings contain the same characters with the same frequencies.
function areAnagrams(first, second) {
if (first.length !== second.length) {
return false;
}
const sortedFirst = first.split("").sort().join("");
const sortedSecond = second.split("").sort().join("");
return sortedFirst === sortedSecond;
}
console.log(areAnagrams("listen", "silent"));
// true
For example:
"listen" → "eilnst"
"silent" → "eilnst"
Same sorted representation → anagram
Frequency Map vs Sorting
| Approach | Time | Extra Space | Main Advantage |
|---|---|---|---|
| Frequency map | O(n) average | O(k) | Linear average-time counting |
| Sorting | O(n log n) | Depends on implementation | Simple and intuitive |
Here, n represents the length of the strings and
k represents the number of distinct characters.
For many interview problems, the frequency-map approach is preferred when the goal is
to achieve linear average time.
Case-Insensitive Anagrams
If uppercase and lowercase letters should be treated as equivalent, normalize both strings before comparing them.
function areAnagrams(first, second) {
first = first.toLowerCase();
second = second.toLowerCase();
if (first.length !== second.length) {
return false;
}
const frequency = {};
for (const char of first) {
frequency[char] = (frequency[char] || 0) + 1;
}
for (const char of second) {
if (!frequency[char]) {
return false;
}
frequency[char]--;
}
return true;
}
console.log(areAnagrams("Listen", "Silent"));
// true
Normalization should always match the requirements. If case is meaningful in the application, changing the case before comparison would produce a different result.
Ignoring Spaces and Punctuation
Some anagram problems compare phrases rather than individual words. In those cases, spaces and punctuation may need to be ignored.
function normalize(text) {
return text
.toLowerCase()
.replace(/[^a-z0-9]/g, "");
}
function areAnagrams(first, second) {
first = normalize(first);
second = normalize(second);
if (first.length !== second.length) {
return false;
}
const frequency = {};
for (const char of first) {
frequency[char] = (frequency[char] || 0) + 1;
}
for (const char of second) {
if (!frequency[char]) {
return false;
}
frequency[char]--;
}
return true;
}
console.log(
areAnagrams(
"Dormitory",
"Dirty room"
)
);
// true
Fixed Character Sets
If the problem guarantees a small fixed alphabet, such as lowercase English letters, an array of 26 counters can replace a hash map. Each letter is mapped to an index between 0 and 25.
function areAnagrams(first, second) {
if (first.length !== second.length) {
return false;
}
const count = new Array(26).fill(0);
for (let i = 0; i < first.length; i++) {
count[first.charCodeAt(i) - 97]++;
count[second.charCodeAt(i) - 97]--;
}
return count.every(value => value === 0);
}
console.log(areAnagrams("listen", "silent"));
// true
This technique is efficient when the input constraints are known. It should not be used blindly for arbitrary Unicode text or inputs containing characters outside the expected alphabet.
Anagram Groups
The same idea can be extended from comparing two strings to grouping many strings into sets of anagrams. One common approach is to create a canonical key for each word, such as its sorted character sequence.
Input:
["eat", "tea", "tan", "ate", "nat", "bat"]
Possible groups:
["eat", "tea", "ate"]
["tan", "nat"]
["bat"]
The words eat,
tea, and
ate all produce the same sorted key:
aet. That shared key can be used to place them
in the same group.
Edge Cases
A robust anagram implementation should consider the input rules and boundary cases.
- Two empty strings are anagrams under the usual definition.
- Strings with different lengths cannot be anagrams.
- Repeated characters must have matching frequencies.
- Case sensitivity depends on the requirements.
- Spaces and punctuation may or may not count.
- Unicode characters may require application-specific normalization rules.
Common Mistakes
- Checking only whether both strings contain the same distinct characters without checking their frequencies.
- Forgetting that repeated characters matter.
- Treating strings with different lengths as possible anagrams.
- Normalizing one string but not the other.
- Assuming uppercase and lowercase characters are always equivalent.
- Using a fixed alphabet array when the input can contain arbitrary characters.
Anagram Problems in Interviews
Anagram questions often test whether you can identify a useful representation of unordered data. The original character order does not matter, so storing frequencies removes that unnecessary ordering information.
A useful interview thought process is:
- Check whether the input sizes can immediately rule out an anagram.
- Clarify case, spaces, punctuation, and character-set rules.
- Choose a frequency map or a sorting-based representation.
- Compare the resulting character information.
- State the time and space complexity of the chosen solution.
Complexity Analysis
Let n be the length of each string and
k be the number of distinct characters.
A frequency-map solution processes each string once.
| Approach | Time | Space |
|---|---|---|
| Hash map | O(n) average | O(k) |
| Sorting | O(n log n) | Depends on implementation |
| Fixed alphabet array | O(n) | O(1) |
The fixed-alphabet approach uses constant auxiliary space only because the character set has a predetermined size. For a general character set, the required frequency storage can grow with the number of distinct characters.
Key Takeaways
- Anagrams contain the same characters with the same frequencies.
- Character order does not matter when determining whether two strings are anagrams.
- A frequency map provides an O(n) average-time solution.
- Sorting both strings provides a simple O(n log n) solution.
- A fixed-size frequency array works well for a known alphabet.
- Case, spaces, punctuation, and Unicode rules should be defined before comparison.
- Frequency counting is also useful for grouping anagrams and solving related string problems.