Counter, defaultdict & deque
Reach for specialized collections: count with Counter, group with defaultdict, queue with deque.
Reach for the right container, not a hand-rolled dict
When you tally, group, or queue, a plain dict or list works, but it forces you to write boilerplate that hides bugs. The collections module ships three focused upgrades that name your intent and delete that boilerplate: Counter for frequencies, defaultdict for grouping, and deque for queues.
| Job | The manual version | The upgrade | What the upgrade removes |
|---|---|---|---|
| Tally frequencies | d[k] = d.get(k, 0) + 1 | Counter(items) | the get-with-default dance on every increment |
| Group into buckets | if k not in d: d[k] = [] | defaultdict(list) | the existence check before every append |
| Pop from the front | lst.pop(0), which is O(n) | deque.popleft(), O(1) | a hidden quadratic in any queue loop |
In interviews and in real data pipelines, reaching for the right one signals you know the standard library, and it usually cuts genuine complexity, not just line count.
Counter: build on the intro
The Modules, imports and the standard library lesson already introduced Counter and most_common(k). Here it earns its place next to defaultdict and deque, so focus on the two properties you lean on when tallying:
from collections import Counter
c = Counter(["a", "b", "a", "c", "a"]) # Counter({'a': 3, 'b': 1, 'c': 1})
c["a"] # 3
c["z"] # 0, not a KeyError
A missing key returns 0 instead of raising, so you never guard a read. And because Counter is a dict subclass, Counter(words) == {"a": 2, "b": 1} is True, so for the Apply exercise you can return the Counter directly, or wrap it in dict(...) if a caller insists on a plain dict.
defaultdict: group without the missing-key dance
A plain dict raises KeyError on a missing key, so grouping needs an if key not in d: d[key] = [] guard. defaultdict(list) calls the factory you pass (the callable list, not list()) the first time a key is touched:
from collections import defaultdict
groups = defaultdict(list)
for w in ["apple", "ant", "bee"]:
groups[w[0]].append(w)
dict(groups) # {'a': ['apple', 'ant'], 'b': ['bee']}
Keys keep insertion order and each list keeps append order, which is exactly what the Practice exercise checks.
deque: a real queue
A deque (double-ended queue) adds and removes at both ends in O(1):
from collections import deque
q = deque([1, 2, 3])
q.appendleft(0) # deque([0, 1, 2, 3])
q.popleft() # 0
Pitfalls
- Merely reading a missing
defaultdictkey inserts it: touchingd["x"]whenxis absent leavesd["x"] == []behind. Used.get("x")when you only want to peek without mutating. defaultdict([])raisesTypeError. The factory must be callable, so passlist,set, orint, never an instance.Counternever raises on a missing key, which is handy but hides typos:c["speling"]quietly returns0.
Interview nuance: using a list as a queue is a classic trap. list.pop(0) and list.insert(0, x) are O(n) because every remaining element shifts one slot, so a BFS built on list.pop(0) is secretly O(n²). deque.popleft() and deque.appendleft() are O(1), which is why a deque is the standard queue for BFS and sliding-window problems. The tradeoff is that a deque has no O(1) random indexing into its middle, unlike a list.
from collections import Counter, defaultdict
print(Counter(["a", "b", "a", "c", "a"])) # Counter({'a': 3, 'b': 1, 'c': 1})
groups = defaultdict(list)
for w in ["ant", "apple", "bee"]:
groups[w[0]].append(w)
print(dict(groups)) # {'a': ['ant', 'apple'], 'b': ['bee']}Apply
Your turn
The task this lesson builds to.
Implement word_counts(words): return a dict mapping each word to how many times it appears in the
list words. Use Counter.
For ["a", "b", "a"] return {"a": 2, "b": 1}.
2 hints and 3 automated checks are waiting in the workspace.
Practice
Make it stick
A second problem on the same idea, so it survives past today.
Implement group_by_first(words): return a dict mapping each first letter to the list of words
starting with it, preserving order. Use defaultdict(list).
["apple", "ant", "bee"] returns {"a": ["apple", "ant"], "b": ["bee"]}.
3 hints and 3 automated checks are waiting in the workspace.