Counter & defaultdict
Reviewed & published by Brayan K
Counter and defaultdict are specialized dictionaries from the collections module: Counter tallies how often each item appears in one line, while defaultdict auto-creates a starting value for new keys so grouping and counting need no boilerplate checks.
Part of the free Python course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
Counting and grouping are two of the most common tasks in all of programming. These two tools turn a fiddly ten-line loop into a clean one-liner.
What You'll Learn in This Lesson
- Tallying any iterable with collections.Counter
- Finding the top items with most_common()
- Adding and subtracting Counters like math
- Grouping with defaultdict(list)
- Counting with defaultdict(int)
- When to prefer defaultdict over setdefault
1 Counting in One Line with Counter
Hand Counter any iterable — a list, a string, anything you can loop over — and it returns a dictionary of counts. The most_common() method then hands back the top items already sorted:
from collections import Counter
votes = ["red", "blue", "red", "green", "blue", "red"]
tally = Counter(votes)
print(tally) # Counter({'red': 3, 'blue': 2, 'green': 1})
# Counting characters in a string just works
letters = Counter("mississippi")
print(letters) # Counter({'i': 4, 's': 4, 'p': 2, 'm': 1})
# most_common(n) returns the top n as (item, count) tuples
print(tally.most_common(2)) # [('red', 3), ('blue', 2)]
# Missing keys safely return 0, never a KeyError
print(tally["purple"]) # 0from collections import Counter
# Find the 3 most common words in a sentence
text = "the cat sat on the mat the cat ran"
words = text.split()
counts = Counter(words)
print(counts.most_common(3))
# ✅ Expected output:
# [('the', 3), ('cat', 2), ('sat', 1)]2 Counter Arithmetic
Counters support + and - to combine tallies element by element — perfect for merging counts from two days, or finding what changed. You can also update() to add more items in place:
from collections import Counter
monday = Counter(apple=4, pear=2, plum=1)
tuesday = Counter(apple=1, pear=5, kiwi=3)
# Add two Counters: counts combine per key
total = monday + tuesday
print(total) # Counter({'pear': 7, 'apple': 5, 'kiwi': 3, 'plum': 1})
# Subtract: keeps only positive results
diff = tuesday - monday
print(diff) # Counter({'pear': 3, 'kiwi': 3})
# update() ADDS counts (it does not overwrite like a plain dict)
monday.update(["apple", "apple"])
print(monday["apple"]) # 6Note that Counter.update() adds to existing counts rather than replacing values — the opposite of a normal dictionary's update(). That's exactly what you want when tallying.
3 Grouping & Counting with defaultdict
A defaultdict takes a factory — a callable like list, int, or set — that produces the starting value for any key you touch for the first time. That means you can append or += without ever checking whether the key exists:
from collections import defaultdict
# defaultdict(list) -> new keys start as []
by_first = defaultdict(list)
for name in ["Ada", "Ben", "Bea", "Amir"]:
by_first[name[0]].append(name) # no setdefault needed!
print(dict(by_first))
# {'A': ['Ada', 'Amir'], 'B': ['Ben', 'Bea']}
# defaultdict(int) -> new keys start as 0
counts = defaultdict(int)
for ch in "banana":
counts[ch] += 1
print(dict(counts)) # {'b': 1, 'a': 3, 'n': 2}
# defaultdict(set) -> new keys start as an empty set
seen = defaultdict(set)
seen["evens"].add(2)
seen["evens"].add(2) # duplicate ignored
print(dict(seen)) # {'evens': {2}}from collections import defaultdict
# Group students by their grade letter
records = [("Ada", "A"), ("Ben", "B"), ("Cleo", "A"), ("Dax", "B"), ("Eve", "A")]
groups = defaultdict(list)
for name, grade in records:
groups[grade].append(name)
for grade in sorted(groups):
print(grade, "->", groups[grade])
# ✅ Expected output:
# A -> ['Ada', 'Cleo', 'Eve']
# B -> ['Ben', 'Dax']🎯 Your Turn: Fill in the Blanks
Replace each ___ so the code counts each word length using a defaultdict(int).
# 🎯 YOUR TURN: count how many words have each length
from collections import defaultdict
words = ["hi", "yo", "code", "fun", "go", "data"]
by_length = defaultdict(___) # factory that starts new keys at 0
for w in words:
by_length[len(w)] ___ 1 # increment the count
print(dict(by_length))
# Expected output:
# {2: 3, 4: 2, 3: 1}Common Errors
c = Counter("hello") # NameError: name 'Counter' is not defined✅ Import from collections first
from collections import Counter
c = Counter("hello")❌ Passing the factory result instead of the factory
d = defaultdict(list()) # TypeError: first argument must be callable or None✅ Pass the function itself, no parentheses
d = defaultdict(list) # pass list, not list()❌ Expecting Counter.update() to overwrite
c = Counter(a=5)
c.update({"a": 1})
print(c["a"]) # 6, not 1 — update() ADDS for Counters✅ Assign directly to set an exact count
c["a"] = 1 # now it's exactly 1🎯 Mini Challenge: Anagram Buckets
Group words that are anagrams of each other. The sorted letters of a word make a perfect grouping key.
# 🎯 Mini Challenge: bucket words into anagram groups
from collections import defaultdict
words = ["listen", "silent", "enlist", "google", "gogole", "cat"]
groups = defaultdict(list)
for w in words:
key = "".join(sorted(w)) # same letters -> same key
groups[key].append(w)
# Print groups that have more than one word
for members in groups.values():
if len(members) > 1:
print(members)
# Expected output:
# ['listen', 'silent', 'enlist']
# ['google', 'gogole']📚 Keep Learning
- Python docs: collections.Counter
- Python docs: collections.defaultdict
- Python docs: defaultdict examples & recipes
Frequently Asked Questions
Lesson complete — counting and grouping are now trivial!
You can tally any iterable with Counter, rank results with most_common(), combine tallies with Counter arithmetic, and group or count effortlessly with defaultdict(list) and defaultdict(int) — no more boilerplate "is this key here yet?" checks.
Practice quiz
What does collections.Counter do when you pass it an iterable?
- Sorts it
- Counts how many times each element appears
- Removes duplicates
- Reverses it
Answer: Counts how many times each element appears. Counter tallies an iterable, returning a dict-like mapping of {element: count}.
What does Counter('mississippi') produce for the letter 'i'?
- A count of 1
- A count of 2
- A count of 4
- A KeyError
Answer: A count of 4. There are four 'i' characters in 'mississippi', so Counter records 'i': 4.
What does counter.most_common(2) return?
- The two rarest items
- The top 2 items as (item, count) tuples, sorted by count
- A count of 2
- The first two keys inserted
Answer: The top 2 items as (item, count) tuples, sorted by count. most_common(n) returns the n highest-count items as (item, count) tuples, already sorted from most to least common.
Accessing a missing key in a Counter, like tally['purple'], returns what?
- A KeyError
- None
- 0
- An empty string
Answer: 0. A Counter returns 0 for keys it has never seen, rather than raising KeyError.
How does Counter.update() behave differently from a plain dict's update()?
- It overwrites values
- It ADDS to existing counts instead of replacing them
- It deletes keys
- It does nothing for Counters
Answer: It ADDS to existing counts instead of replacing them. Counter.update() adds to existing counts (so update on a=5 with a=1 gives 6), the opposite of a normal dict's overwriting update().
What does the factory in defaultdict(int) produce for a brand-new key?
- None
- An empty list
- 0
- An empty string
Answer: 0. defaultdict(int) calls int() for new keys, producing 0 — so you can immediately do counts[key] += 1.
With defaultdict(list), why can you call d[key].append(x) on a new key?
- append creates the key
- A new key automatically starts as an empty list []
- list keys are pre-created
- It silently fails
Answer: A new key automatically starts as an empty list []. The list factory gives any new key a fresh [] on first access, so you can append without checking whether the key exists.
What is the catch with merely READING a missing key in a defaultdict?
- It raises KeyError
- It triggers the factory and inserts that key
- It returns None without inserting
- It deletes the dict
Answer: It triggers the factory and inserts that key. Reading d[missing] runs the factory and adds the key — handy for appending, but a typo while reading silently creates an entry. Use 'in' or .get() to check safely.
Which is the correct way to build a defaultdict of lists?
- defaultdict(list())
- defaultdict([])
- defaultdict(list)
- defaultdict('list')
Answer: defaultdict(list). Pass the factory itself — the callable list, not list() or []. defaultdict(list()) raises TypeError because list() is not callable.
Given the Counters monday=Counter(apple=4) and tuesday=Counter(apple=1), what is (monday + tuesday)['apple']?
- 3
- 4
- 5
- 1
Answer: 5. Adding two Counters combines counts per key, so apple becomes 4 + 1 = 5.
Continue this course
- Previous: Dictionary Methods Deep Dive
- Next: deque & Queues — O(1) appends and pops at both ends with collections.deque
- Quick reference: Python cheat sheet › Standard Library
Frequently asked questions
What does collections.Counter actually do?
Counter is a dict subclass built for tallying. You hand it any iterable and it counts how many times each element appears, storing the results as {element: count}. It also adds handy methods like most_common(n) to get the top items, and supports arithmetic so you can add or subtract two Counters element by element.
When should I use defaultdict instead of a plain dict?
Use defaultdict when you're grouping or accumulating and want a sensible empty value created automatically for keys you haven't seen. defaultdict(list) gives a fresh [] for new keys so you can append immediately; defaultdict(int) gives 0 so you can += to count. It removes the boilerplate of checking 'is this key here yet?'.
How is defaultdict different from dict.setdefault()?
Both supply a default for missing keys, but defaultdict bakes the default into the dictionary itself via a factory function, so every access just works. setdefault provides the default on each call and creates the default object every time even when the key exists. For repeated grouping, defaultdict is cleaner and slightly faster.
Does accessing a missing key in a defaultdict create it?
Yes — and that's the catch. Simply reading d[missing_key] triggers the factory and inserts the new key with its default value. That's exactly what makes appending and counting effortless, but it means a typo while reading can silently add an entry. Use the in operator or .get() if you only want to check without inserting.