Lesson 10: Advanced Collections — Counter, defaultdict & deque
Supercharge your data with Counter, defaultdict, deque, and namedtuple from the collections module.
The collections Module — Professional Tools
The collections module ships with specialized containers that solve common problems in one line. Once you know them, you will wonder how you lived without them. This lesson covers the four you will use constantly.
These containers are pure Python's answer to the "I keep writing the same boilerplate" problem — every one of them replaces several lines of manual logic with a single, readable construction.
What You'll Learn in This Lesson
- Tally anything instantly with
Counter - Stop fearing missing keys with
defaultdict - Build fast queues with
deque - Create self-documenting records with
namedtuple
Counter — Instant Tallies
Counter counts hashable items and gives you .most_common() for free:
from collections import Counter
votes = ["python", "python", "ai", "ml", "ai", "ai"]
tally = Counter(votes)
print(tally) # Counter({'ai': 3, 'python': 2, 'ml': 1})
print(tally.most_common(2)) # [('ai', 3), ('python', 2)]
It also works on any iterable — including strings (counts characters):
print(Counter("mississippi").most_common(1)) # [('s', 4)]
Mental model: a tally sheet where every item adds one tick.
Useful Counter operations:
| Operation | Code | Result for votes above |
|---|---|---|
| Total count | sum(tally.values()) |
6 |
| Most common n | tally.most_common(2) |
[('ai', 3), ('python', 2)] |
| Add two counters | tally + Counter(["ai"]) |
{'ai': 4, ...} |
| Top item | tally.most_common(1)[0][0] |
'ai' |
Counters power frequency analysis in everything from word clouds to vote counting to sales reports.
defaultdict — Never Fear Missing Keys
A normal dict raises KeyError on missing keys. A defaultdict returns a default value instead — automatically creating the entry:
from collections import defaultdict
teams = defaultdict(list)
teams["IT"].append("Amol") # no KeyError — key auto-created!
teams["IT"].append("Sam")
teams["HR"].append("Riya")
print(dict(teams)) # {'IT': ['Amol', 'Sam'], 'HR': ['Riya']}
The argument to defaultdict is a factory — a function that creates the default:
| Factory | Default value |
|---|---|
defaultdict(list) |
[] |
defaultdict(int) |
0 |
defaultdict(set) |
set() |
defaultdict(dict) |
{} |
This removes endless if key not in dict: boilerplate — ideal when grouping data.
Compare the two styles:
# Manual — 4 lines of ceremony per group
groups = {}
for dept, name in employees:
if dept not in groups:
groups[dept] = []
groups[dept].append(name)
# defaultdict — 3 lines total, no ceremony
groups = defaultdict(list)
for dept, name in employees:
groups[dept].append(name)
deque — Fast Operations on Both Ends
A deque (double-ended queue) gives O(1) append/pop on both ends. Lists are O(n) for left-side operations; deques are instant:
from collections import deque
queue = deque(["a", "b"])
queue.append("c") # add to right end
queue.appendleft("z") # add to left end
queue.popleft() # remove from left
print(list(queue)) # ['a', 'b', 'c']
| List | deque | |
|---|---|---|
| Append right | O(1) | O(1) |
| Pop left / insert left | O(n) | O(1) |
| Random access | O(1) | O(n) |
Use deque for queues, recent-history buffers, and any structure needing fast left-end operations. deque(maxlen=100) auto-drops the oldest item — a rolling buffer.
Mental model: a deque is a train with doors at both ends — boarding and leaving is instant at either end, unlike a list where people at the front must shuffle over.
namedtuple — Self-Documenting Data
namedtuple creates lightweight objects with named fields — cleaner than a plain tuple, lighter than a class:
from collections import namedtuple
Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)
print(p.x, p.y) # 3 4 — access by name
print(p[0]) # 3 — still works by index
Perfect for coordinates, records, and configuration — you get readability without writing a full class.
namedtuple vs dataclass: if you need just named fields with no methods, namedtuple is ideal. If you want defaults, validation, or methods, the @dataclass decorator (Lesson 19) is the modern choice.
Common Mistakes to Avoid
- Mistake: Passing an unhashable (like a list) to
Counter— Fix: Counter needs hashable items. - Mistake: Reading from a
defaultdictand unintentionally creating keys — Fix: usedefaultdictonly for writes, or use.get()when reading counts. - Mistake:
queue[0]style random access on a huge deque — Fix: deques are for ends; use a list for random access. - Mistake: Using a plain dict and re-writing the
if key not inceremony — Fix: reach fordefaultdictwhen grouping. - Mistake:
namedtuplefield names that clash with keywords (e.g.class) — Fix: rename the field (cls).
Professional Tips & Tricks
most_common(n)turns 'analyze frequencies' into one line.defaultdict(list)is the go-to for grouping — dict comprehension of lists, without boilerplate.- Use
deque(maxlen=100)for a rolling buffer that auto-drops the oldest item. - Use
namedtuplefor return values that should be readable at a glance. - Counters support arithmetic (
+,-) — combine tallies from different sources.
Key Takeaways
Countertallies items and ranks them with.most_common().defaultdict(factory)auto-creates missing keys.dequegives O(1) operations on both ends — the right tool for queues.namedtuplegives named fields with tuple performance.deque(maxlen=n)makes rolling buffers effortless.
Next up: Nested structures, aliasing & copies — where beginners lose hours.
from collections import Counter, defaultdict, deque, namedtuple
# Counter: tally votes
votes = ["python", "python", "ai", "ml", "ai", "ai"]
tally = Counter(votes)
print("Tally:", tally)
print("Top 2:", tally.most_common(2))
# defaultdict: group by department
employees = [("IT", "Amol"), ("HR", "Riya"), ("IT", "Sam")]
teams = defaultdict(list)
for dept, name in employees:
teams[dept].append(name)
print("Teams:", dict(teams))
# deque: fast queue
queue = deque(["a", "b"])
queue.append("c")
queue.popleft()
print("Queue:", list(queue))
# namedtuple: readable records
Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)
print("Point:", p.x, p.y)Lesson Code (Python)
from collections import Counter, defaultdict, deque, namedtuple
# Counter: tally votes
votes = ["python", "python", "ai", "ml", "ai", "ai"]
tally = Counter(votes)
print("Tally:", tally)
print("Top 2:", tally.most_common(2))
# defaultdict: group by department
employees = [("IT", "Amol"), ("HR", "Riya"), ("IT", "Sam")]
teams = defaultdict(list)
for dept, name in employees:
teams[dept].append(name)
print("Teams:", dict(teams))
# deque: fast queue
queue = deque(["a", "b"])
queue.append("c")
queue.popleft()
print("Queue:", list(queue))
# namedtuple: readable records
Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)
print("Point:", p.x, p.y)Console Output
Tally: Counter({'ai': 3, 'python': 2, 'ml': 1})
Top 2: [('ai', 3), ('python', 2)]
Teams: {'IT': ['Amol', 'Sam'], 'HR': ['Riya']}
Queue: ['b', 'c']
Point: 3 4Code Visualization Tips
- Picture Counter as a tally sheet where every item adds one tick.
- Visualize defaultdict as a table that auto-fills an empty cell with a default the first time it is touched.
- Draw a deque as a train with doors at both ends — boarding and leaving is instant at either end.
Professional Tips & Tricks
- most_common(n) turns 'analyze frequencies' into one line.
- defaultdict(list) is the go-to for grouping — dict comprehension of lists, without boilerplate.
- Use deque(maxlen=100) for a rolling buffer that auto-drops the oldest item.
Python Code Judge & Practice Arena
LeetCode StyleRun real Python 3.12 WebAssembly code directly in your browser against automated test suites.
Problem 1: Top-K Frequent Elements 1
Test Your Knowledge
Instant feedbackQuick Check: Lesson 10: Advanced Collections: defaultdict, Counter, deque & namedtuple
Up next · Continue learning
Nested Structures, Aliasing & Copies
Build nested dicts and lists, understand how references behave, and copy safely.