Algorithm

Group Anagrams

Arrays & Strings Pattern

Group Anagrams

Given an array of strings strs, group the anagrams together. You can return the answer in any order.

CONSTRAINTS
  • 1 <= strs.length <= 10⁴
  • 0 <= strs[i].length <= 100
  • strs[i] consists of lowercase English letters only
EXAMPLE 1
Input: strs = ["eat","tea","tan","ate","nat","bat"]
Output: [["bat"],["nat","tan"],["ate","eat","tea"]]
Each group holds words made of the exact same letters in the same counts. 'bat' shares its letters with no other word, so it stands alone.
EXAMPLE 2
Input: strs = [""]
Output: [[""]]
The single empty string forms one group by itself; there is nothing else to be an anagram of.
EXAMPLE 3
Input: strs = ["a"]
Output: [["a"]]
One word means one group. Any ordering of the output is accepted.
Does the order of the groups, or of the words within a group, matter?
No. Any ordering of the outer list and of the inner lists is accepted; only the partition into anagram families matters.
Can the input contain empty strings, and how are they grouped?
Yes. Every empty string has the empty key, so all empty strings land in one group together.
Sorted-string key or letter-count key — which should I use?
Both are correct. Sorting is O(K log K) per word and shortest to write; a 26-slot count key is O(K) and faster for long words, but must be encoded with delimiters to avoid collisions.

We are given a list of words and must gather them into groups, where each group holds words that are anagrams of one another — same letters, same counts, order irrelevant — like "eat", "tea", "ate". The groups may come back in any order, and the words within a group in any order; all that matters is the partition. This is the plural of Valid Anagram: there we answered "are these two words anagrams?", here we must sort a whole list into anagram families at once.

Compare every pair? Count the cost

The direct extension of Valid Anagram: keep a list of groups, and for each new word walk the existing groups, run the two-word anagram check against a representative of each, and drop the word into the first group it matches — or start a new group if none do. Correct, but count it. With N words, a word may be compared against up to N existing representatives, and each anagram check scans about K characters (K the word length). That is O(N² × K), and the wasted effort is the repeated pairwise testing: we keep re-establishing "these two are anagrams" from scratch, one pair at a time, when belonging to a family is really a property of the single word.

One key per word: the census, reused

The fix reuses the exact insight from Valid Anagram — an anagram is defined by its letter census, not its arrangement. So give every word a canonical key: a fingerprint that is identical for all anagrams of the word and different for everything else. If two words share the key, they are anagrams, no pairwise comparison needed. A single hash map keyed by that fingerprint then drops each word straight into its family in one pass — the same "spend memory to skip repeated scanning" trade behind Two Sum, applied to grouping.

Two natural fingerprints:
- Sorted letters. Sorting "eat", "tea", and "ate" all yields "aet". Cost: O(K log K) per word to sort.
- Letter counts. A 26-number tally — the very tally sheet from Valid Anagram — encoded into a string is identical for anagrams. Cost: O(K) per word, no sort.

Bucketing by canonical key

Walk the list once. Compute each word's key and append the word to the map entry under that key. At the end, the map's values are the groups.

python
from collections import defaultdict

def group_anagrams(strs):
    groups = defaultdict(list)
    for word in strs:
        key = "".join(sorted(word))   # canonical: anagrams share it
        groups[key].append(word)
    return list(groups.values())
Crucial Notewhatever key you choose, it must be unambiguous — one fingerprint per anagram family, with no accidental collisions. Sorting is automatically safe. But a key built from letter counts must be encoded with separators: gluing raw counts together turns {a: 1, b: 12} and {a: 11, b: 2} both into "112", wrongly merging two different families. A delimiter ("1#12#…" versus "11#2#…") keeps them distinct. This is the classic counting-key bug.
Worked Example:["eat", "tea", "tan", "ate", "nat"]
Using the sorted-letters key:
- "eat" → key "aet". Map: {aet: ["eat"]}.
- "tea" → key "aet". Same bucket: {aet: ["eat", "tea"]}.
- "tan" → key "ant". New bucket: {aet: [...], ant: ["tan"]}.
- "ate" → key "aet". {aet: ["eat", "tea", "ate"], ant: ["tan"]}.
- "nat" → key "ant". {aet: [...], ant: ["tan", "nat"]}.
- Groups (the map's values): ["eat", "tea", "ate"] and ["tan", "nat"]. An empty string, if present, keys to "" and forms its own bucket — all empty strings are anagrams of each other.
The trade, and the grouping reflex

One pass builds the answer: O(N × K log K) with the sorted key, or O(N × K) with the count key — either way a decisive win over the O(N² × K) pairwise approach, paid for with O(N × K) space to hold the map. The reflex worth internalizing reaches far past anagrams: whenever you must group items by an equivalence relation ("same when reordered", "same when normalized", "same shape"), do not compare pairs — invent a canonical form that is identical exactly for equivalent items, and hash on it. Finding that canonical form is the whole game; here it was the letter census you already met in Valid Anagram.

Interactive Strategy Visualization

anagram grouping

Fingerprinting by Sorted Key
Input List
eat
tea
tan
ate
Map (Key → Group)
Map is empty...
🧪

Ready to group anagrams from the input list.

O(N² × K) Pairwise
O(N × K log K) Sort Key
O(N × K) Count Key