Skip to content
← Back to all lessons
Day 005 · June 28, 2026 Algorithms

Valid Anagram — the frequency count pattern

When the question is 'how many times?', a hash map is the answer.

8 min read

Day 4 taught you the hash set pattern: “have I seen this before?” → O(1) membership check. Today we level up to the hash map pattern: “how many times does this appear?” → O(1) count update. Same hash-based family, different question.

The problem

Input:  s = "anagram", t = "nagaram"
Output: true  (same letters, same counts)

Input:  s = "rat", t = "car"
Output: false (different letters)

An anagram is a rearrangement. Same ingredients, different order. The question: do these two strings have the same character counts?

Approach 1: Sort both, compare — O(n log n)

The obvious approach: sort both strings. If they’re anagrams, the sorted versions will be identical.

function isAnagram(s, t) {
  if (s.length !== t.length) return false;
  return s.split("").sort().join("") === t.split("").sort().join("");
}

Clean and readable. But sorting is O(n log n) time and O(n) spacesplit("") creates an O(n) array, and the sort itself uses O(n) auxiliary memory. You’re doing more work and using more memory than necessary. You don’t need the strings ordered; you just need to know if the counts match.

Approach 2: Hash map frequency count — O(n)

Instead of sorting, count the characters. Walk through s, incrementing a count for each character. Then walk through t, decrementing. If all counts hit zero, it’s an anagram.

function isAnagram(s, t) {
  if (s.length !== t.length) return false;
  const count = new Map();
  for (const c of s) count.set(c, (count.get(c) || 0) + 1);
  for (const c of t) {
    if (!count.has(c)) return false;
    count.set(c, count.get(c) - 1);
    if (count.get(c) < 0) return false;  // more of this char in t than s
  }
  return true;
}

Two passes, O(1) work per character. That’s O(n) time. The hash map stores up to (alphabet size) entries — O(k) space where k is the number of distinct characters.

Approach 3: Fixed array — O(n), O(1) space

Here’s the optimization most candidates miss. If the alphabet is fixed (26 lowercase letters), you don’t need a hash map at all — use an array of 26 integers, indexed by character code.

function isAnagram(s, t) {
  if (s.length !== t.length) return false;
  const count = new Array(26).fill(0);
  for (const c of s) count[c.charCodeAt(0) - 97]++;
  for (const c of t) {
    const i = c.charCodeAt(0) - 97;
    count[i]--;
    if (count[i] < 0) return false;
  }
  return true;
}

Same O(n) time, but O(1) space — the array is always 26 integers, regardless of input size. And no hashing overhead: array indexing is direct memory access.

The real benchmark

I ran all three approaches on strings of increasing size (30 iterations each, median):

Real benchmark — median of 30 runs (y-axis NOT to scale)string length (n)time (ms)1K1.50.910K16.45.91.2100K16165151MSort O(n log n)Hash map O(n)Fixed array O(n)

Three key observations from the benchmark:

  • At small n, sort can win. At n=1,000, sort (0.12ms) beats hash map (0.24ms). V8’s sort is native C; the hash map has JS-level overhead. Big O describes asymptotic growth — at small n, constant factors dominate.
  • At scale, O(n) beats O(n log n). At n=1,000,000, sort (161ms) is 2.5× slower than hash map (65ms). The gap grows with n.
  • Same Big O, different constants. Hash map (65ms) and fixed array (15ms) are both O(n), but the array is 4.3× faster at n=1M. Array indexing is direct memory access; hash map has hashing overhead. And the array uses O(1) space vs O(k) for the map.

The pattern: “count and compare”

When you need to compare the composition of two collections (same characters? same word counts?), use a hash map to count frequencies, then compare. O(n) time, O(k) space.

If the keyspace is bounded (26 letters, 10 digits, fixed enum), replace the hash map with a fixed array — same O(n) time, O(1) space, lower constant.

This pattern appears in: Group Anagrams (count each string’s character frequencies, use as a key), Top K Frequent Elements (count frequencies, then select the top k), Ransom Note (count magazine letters, subtract ransom letters). Once you internalize “count and compare,” these all become two-pass solutions.

The transfer question

From Day 4 → Day 5: When would you use a hash set vs a hash map? What’s the question each one answers?

Answer (click to reveal)

Use a hash set when the question is binary: “is this value present?” — you only need membership, not quantity. Example: Contains Duplicate (Day 4) — you only need to know “have I seen 1 before?”, not “how many 1s?”

Use a hash map when the question is quantitative: “how many times does this appear?” — you need a count per key. Example: Valid Anagram (Day 5) — you need to know “are there 3 a’s in both strings?”, not just “is a present?”

The rule of thumb: if you’re writing if (seen.has(x)), use a set. If you’re writing count.set(x, count.get(x) + 1), use a map.

Check your understanding

1. What is the time complexity of the sort-then-compare approach?

2. Why is the fixed array O(1) space when the hash map is O(k)?

3. Which question does a hash MAP answer that a hash SET cannot?

Your turn — the teach step Close this lesson. Write the “Explain like I’m 10” and the “60-second LinkedIn version” from memory. Focus on the pattern, not the code. Post it, and paste the link.