Skip to content
← Back to all lessons
Day 012 · July 6, 2026 Algorithms

Group anagrams — sort key vs count key

Two ways to build a canonical key: sort each string (O(n × k log k)) or count characters (O(n × k)). The benchmark reveals a surprising crossover.

4 min read

The problem

LeetCode 49 — Group Anagrams. Given an array of strings, group the anagrams together.

Input:  ["eat","tea","tan","ate","nat","bat"]
Output: [["eat","tea","ate"],["tan","nat"],["bat"]]

“eat”, “tea”, and “ate” are anagrams — same letters, different order. “tan” and “nat” are another group. “bat” is alone.

The core question: how do you know two strings are anagrams without comparing every pair?

If you compare every pair, that’s O(n² × k log k). Too slow. You need a key: a transformation where all anagrams produce the same output. Then you group by that key in O(n).

Approach 1: sort key — O(n × k log k)

Sort each string’s characters. All anagrams produce the same sorted string. Use it as a hash map key.

"eat" → sorted → "aet". "tea" → sorted → "aet". Same key → same group.

function groupAnagrams(strs) {
  const map = new Map();
  for (const s of strs) {
    const key = s.split('').sort().join('');
    if (!map.has(key)) map.set(key, []);
    map.get(key).push(s);
  }
  return [...map.values()];
}

Simple and clean. Sort costs O(k log k) per string, so total time is O(n × k log k).

Approach 2: count key — O(n × k)

Instead of sorting, count character frequencies. Build a key from the counts.

function groupAnagrams(strs) {
  const map = new Map();
  for (const s of strs) {
    const count = new Array(26).fill(0);
    for (const c of s) count[c.charCodeAt(0) - 97]++;
    const key = count.join('#');
    if (!map.has(key)) map.set(key, []);
    map.get(key).push(s);
  }
  return [...map.values()];
}

No sort. Counting is O(k) per string. Total time: O(n × k).

The benchmark

InputSort KeyCount KeyWinner
30 words, len=50.026ms0.054msSort 2.1× faster
250 words, len=80.237ms0.268msSort 1.1× faster
500 words, len=100.683ms0.534msCount 1.3× faster
2,000 words, len=153.502ms2.638msCount 1.3× faster
5,000 words, len=2011.552ms6.878msCount 1.7× faster

The surprise: at n=30, sort is 2.1× FASTER than count — even though count has better Big O. JavaScript’s sort() (TimSort) is highly optimized, while building a 26-element array and joining it has constant-factor overhead that dominates on tiny inputs.

The crossover happens around n=250-500. Above that, count’s O(k) per string beats sort’s O(k log k), and the gap grows.

The hash pattern — Day 12 extends it

DayProblemQuestionStructure
4Contains Duplicate”Seen this?”Hash set
5Valid Anagram”How many?”Hash map (count)
11Two Sum”What do I need?”Hash map (lookup)
12Group Anagrams”Who’s like me?”Hash map (grouping)

The pattern: hash maps answer questions in O(1). The question determines the key. Grouping → canonical form as key, list as value.

Check your understanding

1. Why does sorting each string work as a grouping key?

2. Why is count key O(n × k) while sort key is O(n × k log k)?

3. Sort beat count at n=30 despite worse Big O. Why?