Maps of Collections
The value in a map does not have to be a single number or word. It can be a whole List, which lets one key hold many items at once.
Declaring a Map of Lists
The value type simply becomes a collection type:
Map<String, List<String>> namesByLetter = new Map<String, List<String>>();
Every key now points at its own list, and each of those lists starts out empty until you create it.
The Check and Create Pattern
A map never builds the inner list for you. Before adding to a group, check whether the key exists and put a brand new list there when it does not:
List<String> names = new List<String>{ 'ana', 'ben', 'anna' };
for (String personName : names) {
String letter = personName.substring(0, 1);
if (!namesByLetter.containsKey(letter)) {
namesByLetter.put(letter, new List<String>());
}
namesByLetter.get(letter).add(personName);
}
System.debug(namesByLetter.get('a').size()); // 2
Notice that get() hands back the real list, not a copy, so adding to it updates what the map holds.
Reading a Group Back
A key that never appeared returns null, so guard the read when the group may be missing:
List<String> zNames = namesByLetter.containsKey('z')
? namesByLetter.get('z')
: new List<String>();
System.debug(zNames.size()); // 0
Common Mistakes
Do not call add() straight after get() without the check. On a key the map has never seen, get() returns null and calling add() on it throws a null pointer exception.
Why This Matters
Grouping is one of the most useful shapes in Apex: contacts under each account id, opportunities under each stage, errors under each record. A map of lists holds all of those groups in one pass over the data.