← All Tools

Anagram Detection: Why Character Frequency Beats Sorting

Guide · Last verified Aug 27, 2026

There are two common ways to check whether two strings are made of exactly the same letters (an anagram): sort each string and compare the sorted results, or count how many times each character appears and compare the two frequency tables. They look equivalent on the surface, but their time complexity and implementation pitfalls differ. Based on the actual code behind the Anagram Checker, here's which approach it uses — and what to watch for with characters like Korean syllables, whose components can come apart.

1. Sort-and-compare: the most intuitive but slowest method

The first approach most people think of is splitting each string into an array, sorting both, and checking whether the sorted results match exactly. For example, sorting "listen" gives "eilnst", and sorting "silent" gives the same "eilnst", so the two are judged anagrams. It's intuitive, but sorting itself costs O(n log n) with a comparison-based sort, and it gets needlessly slow as the string grows (say, when comparing entire documents). It looks simple to implement, but producing that intermediate "sorted order" means copying and rearranging the whole original array.

2. Frequency-table comparison: just count and you're done

The second approach walks each string exactly once, accumulating per-character counts into a Map (or array), then compares the two frequency tables. Since no sorting is needed, the traversal itself is O(n), and comparing the two frequency tables costs roughly proportional to the number of distinct characters involved (usually 26 for the alphabet, or at most a few hundred even for Korean), which is effectively linear time overall. Looking at the actual code behind the Anagram Checker, there's no sort function involved at all — it builds the frequency table with a charFreq function like the one below.

Actual code: function charFreq(s){const m=new Map();for(const c of s)m.set(c,(m.get(c)||0)+1);return m;} — it walks the string once, accumulating counts into a Map. The verdict first checks lengths with a.length===b.length, then, across the union of every character that appears in either string, flags the pair as not-an-anagram the moment it finds any character where fa.get(c)!==fb.get(c).

3. Where the performance gap actually becomes noticeable

Comparing two or three words, either method feels instant — no perceptible difference. The gap opens up once the input gets long. Comparing two 10,000-character texts, for example, the sorting approach needs roughly 10,000×log₂(10,000)≈133,000 comparison operations per string, while the frequency-table approach only has to walk each string 10,000 times. The table below shows the rough scale of operations by input length.

Input length (n)Sort compare O(n log n)Frequency compare O(n)
10 chars~33 ops10 ops
1,000 chars~9,966 ops1,000 ops
10,000 chars~132,877 ops10,000 ops

The gap widens with input length because sorting does logarithmically more work per element comparison. For short word-game anagram checks it barely matters, but for tools handling whole texts or log files, the frequency-table approach is clearly the better choice.

4. The real trap with Korean text: jamo decomposition (NFC vs. NFD)

Whichever algorithm you use, the real trap when handling Korean isn't the algorithm — it's "how many actual Unicode code points make up one character." A precomposed Korean syllable like "가" (NFC, Normalization Form C) is a single code point, but the same syllable stored in decomposed form (NFD, Normalization Form D) splits into two code points: the consonant "ㄱ" and the vowel "ㅏ". Both look identical as "가" on screen, but if the internal representation differs, iterating character-by-character produces completely different results. Text pasted in from macOS's filesystem or certain input methods, which can store text as NFD, can trigger exactly this problem.

What we found: The actual code behind the Anagram Checker iterates the string code-point by code-point with for(const c of s), with no .normalize() call. That means if the input is already precomposed (NFC), syllables like "가" and "나" are each counted correctly as one character — but if decomposed (NFD) text enters through any path, the consonant and vowel get tallied as separate characters, so the same word could be judged as having a different character makeup. Ordinary keyboard input and copy-paste is almost always NFC, so this rarely bites in practice, but the code makes no explicit guarantee of normalization.

5. Options handled with a single regex: punctuation, spaces, case

Before comparing, this tool normalizes case, spacing, and punctuation based on the selected options. Punctuation removal uses the Unicode property regex /[^\p{L}\p{N}]/gu, where \p{L} means "letter" (covering not just the Latin alphabet but Korean, Chinese characters, and more) and \p{N} means "number." In other words, it's not filtering only English-style punctuation — it's designed to strip out genuine punctuation and symbols regardless of language, so it works correctly on Korean sentences too. If you want to inspect the full structure of special characters in a text separately, tools like the Duplicate Line Finder or the Unicode Inspector can help you check the source text first.

Frequently Asked Questions

Q. Is it fine to use sorting for short word comparisons?

A. Yes. For words as short as "listen" and "silent," there's essentially no execution-time difference between sorting and frequency tables. The performance gap only becomes noticeable once inputs run into the hundreds or thousands of characters.

Q. Does the frequency-table approach always use less memory too?

A. Not necessarily. The sorting approach only needs a single sorted array of length n, while the frequency-table approach creates one Map entry per distinct character. But since the number of distinct characters is usually far smaller than the string length n (26 for the alphabet, and at most a few hundred even for Korean syllables in practical use), the frequency-table approach is effectively lighter in practice.

Q. Are multi-code-point characters like emoji counted accurately?

A. Most Unicode code points are handled correctly, but for things like flag emoji or emoji combined via ZWJ (Zero Width Joiner), where several code points render as a single visual character, the count splits by code point — so the result can differ from what you'd expect. It's fundamentally the same kind of trap as Korean jamo decomposition (NFD).

Q. Are anagrams and palindromes checked the same way?

A. No. An anagram check compares whether two different strings share the same character makeup, while a palindrome check tests whether a single string reads the same forwards and backwards — the underlying logic is different. You can check for palindromes with the Palindrome Checker.