When you need to count how often items appear or group items by a key, don’t write a loop with an if that checks whether the key already exists. collections.Counter counts frequencies in a single line, while collections.defaultdict groups items by key without any upfront initialization.
Both ship with Python’s standard library, so your code gets shorter, runs faster than a hand-written loop, and reads exactly like what it does. Reach for them whenever you analyze logs, aggregate query results, or process tabular data.
The Problem
Say you have a list of order statuses and want to know how many times each status appears. The manual way usually looks like this:
from collections import Counter, defaultdictstatuses = ["paid", "pending", "paid", "shipped", "paid", "pending"]# Cách thủ công: đếm bằng dict + ifcounts = {}for s in statuses: if s not in counts: counts[s] = 0 counts[s] += 1print(counts) # {'paid': 3, 'pending': 2, 'shipped': 1}
The code works, but it is long and easy to get wrong: forget the if and you hit a KeyError; forget the initialization and you lose data. Grouping data is even worse — grouping orders by customer needs another similar if to create an empty list before calling append.
The Solution
Counter replaces the entire counting loop:
counts = Counter(statuses)print(counts) # Counter({'paid': 3, 'pending': 2, 'shipped': 1})print(counts.most_common(2)) # [('paid', 3), ('pending', 2)]print(counts["cancelled"]) # 0, không lỗi KeyError
To see the 3 most common statuses, just call .most_common(3) — no need to sort anything yourself. For grouping, defaultdict(list) creates an empty list automatically whenever it meets a new key:
orders = [("An", 120_000), ("Bình", 85_000), ("An", 45_000)]by_customer = defaultdict(list)for name, total in orders: by_customer[name].append(total)print(dict(by_customer)) # {'An': [120000, 45000], 'Bình': [85000]}# Đếm bằng defaultdict(int)per_customer = defaultdict(int)for name, total in orders: per_customer[name] += 1print(dict(per_customer)) # {'An': 2, 'Bình': 1}
To count instead of grouping, switch the factory to int: defaultdict(int) starts at 0 and adds up from there. Since both Counter and defaultdict are dict subclasses, any existing code that accepts a dict works with them right away — and when you need a plain dict back, just wrap it in dict(...).
Applying It to Your Project
These two classes shine wherever you keep writing the same if key not in dict loop: counting requests per endpoint from an access log, grouping errors by type for a report, or tallying orders by status before pushing them to a dashboard. One handy trick: Counter supports direct addition — count each batch separately, then add the Counter objects together when processing a large log file, instead of holding all the data in RAM at once.
Related Posts
- Python: Replace os.path with pathlib — Read, Write, and Browse Files More Concisely in 5 Minutes
- Python: Replace Long if/elif Chains with the match Statement — Pattern Matching in 5 Minutes
- Python: Handle Time Zones Correctly with zoneinfo — End Naive/Aware Errors in 5 Minutes
Frequently Asked Questions
How is Counter different from a regular dict?
Counter is a dict subclass: accessing a missing key returns 0 instead of raising KeyError, and it comes with most_common() for grabbing the top N. Every dict operation works on it.
How is defaultdict(list) different from dict.setdefault()?
setdefault() still has to be called explicitly every time, and it creates a new list even when the key already exists. defaultdict creates the default value exactly when it is needed, so the code is shorter and faster.
With very large data, does Counter keep everything in RAM?
Yes — Counter keeps one entry per distinct key. For a large log file, count it in batches and then add the Counter objects together, or keep only the top N with most_common().
When should you not use defaultdict?
When you need a KeyError to catch an unexpected key, or when passing the dict to other code — defaultdict will silently create a new key if it is read by mistake. In that case, convert back to a plain dict with dict(...).
💬 Have you tried this in your own project? If you hit an error or have a shorter way to do it, leave a comment below — I read them all and will add the best ideas to the post.
References
- Python docs — collections: Counter and defaultdict: https://docs.python.org/3/library/collections.html
- Real Python — Python’s Counter: The Pythonic Way to Count Objects: https://realpython.com/python-counter/
