collections.Counter is a dict subclass purpose-built for counting hashable objects: you hand it an iterable (or a mapping, or keyword arguments) and it returns a dictionary that maps each element to how many times it appeared. Reach for it whenever you would otherwise write the classic "check if the key exists, then increment" loop over a plain dict — word and character frequencies, tallying log levels, finding the top-N items, treating a collection as a multiset/bag, or comparing two tallies with arithmetic. It is written in C, so it is both fast and concise.
This is a deep, dedicated guide to Counter for Python 3.13: how to build one, every method worth knowing (most_common, elements, update, subtract, total), the arithmetic and set operations, real-world recipes, a performance comparison against a hand-rolled dict and defaultdict(int), and the gotchas that trip people up. For the other containers in the module — deque, namedtuple, defaultdict, OrderedDict, ChainMap — see the companion overview, Working with Python Collections — Part 1.
Creating a Counter
There are four ways to build a Counter, and they cover almost every situation. The most common is to pass an iterable, which counts each element. You can also pass a mapping of pre-computed counts, keyword arguments, or start empty and fill it later with update().
from collections import Counter
# 1. From an iterable -> counts each element
Counter("mississippi") # Counter({'i': 4, 's': 4, 'p': 2, 'm': 1})
Counter(["a", "b", "a", "c", "a", "b"]) # Counter({'a': 3, 'b': 2, 'c': 1})
# 2. From a mapping of existing counts
Counter({"a": 2, "b": 1}) # Counter({'a': 2, 'b': 1})
# 3. From keyword arguments
Counter(a=2, b=1) # Counter({'a': 2, 'b': 1})
# 4. Empty, then fill it
c = Counter()
c.update("aab") # Counter({'a': 2, 'b': 1})Note that passing a mapping counts nothing — it trusts the numbers you give it — whereas passing a string counts its characters. Counter("aab") is {'a': 2, 'b': 1}, but Counter({"aab": 1}) is {'aab': 1}.
Accessing counts: missing keys return 0
Because Counter overrides __missing__, looking up an element that was never counted returns 0 instead of raising KeyError. Importantly — and unlike defaultdict — reading a missing key does not create it, so a lookup never mutates the counter.
c = Counter("aab") # Counter({'a': 2, 'b': 1})
print(c["a"]) # 2
print(c["z"]) # 0 <- no KeyError for a missing element
print("z" in c) # False <- the lookup did NOT insert 'z'
print(dict(c)) # {'a': 2, 'b': 1}
# Setting a count to 0 does NOT remove the key; use del for that.
c["a"] = 0
print(c) # Counter({'b': 1, 'a': 0})
del c["a"]
print(c) # Counter({'b': 1})most_common(): top-N and bottom-N
most_common(n) returns the n highest-count elements as a list of (element, count) tuples, ordered from most to least common. Call it with no argument to get every element sorted by count. Elements that tie are returned in the order they were first encountered (a guarantee that has held since Python 3.7).
words = Counter("the quick brown fox the lazy dog the".split())
words.most_common(2) # [('the', 3), ('quick', 1)]
words.most_common() # all elements, highest count first
# [('the', 3), ('quick', 1), ('brown', 1), ('fox', 1),
# ('lazy', 1), ('dog', 1)]
# The N LEAST common: take the tail of most_common() and reverse it
words.most_common()[:-2 - 1:-1] # [('dog', 1), ('lazy', 1)]elements(): expanding counts back into items
elements() returns an iterator that yields each element repeated as many times as its count — effectively the inverse of constructing a Counter from an iterable. Elements with a count of zero or less are skipped entirely, and the order follows first-insertion order.
c = Counter(a=2, b=1, c=0, d=-1)
list(c.elements()) # ['a', 'a', 'b'] (c and d are <= 0, so skipped)
# Round-trip: Counter -> elements -> sorted list
sorted(Counter("banana").elements()) # ['a', 'a', 'a', 'b', 'n', 'n']update() and subtract(): combining tallies in place
Counter.update() is not like dict.update(). A plain dict.update() replaces values; Counter.update() adds counts together. Its mirror image, subtract(), subtracts counts and — unlike the - operator — happily keeps zero and negative results.
c = Counter("abc") # Counter({'a': 1, 'b': 1, 'c': 1})
c.update("aab") # ADDS counts (not replace)
print(c) # Counter({'a': 3, 'b': 2, 'c': 1})
# subtract() keeps zero/negative results in place
s = Counter(a=4, b=2, c=0, d=-2)
s.subtract(Counter(a=1, b=2, c=3, d=4))
print(s) # Counter({'a': 3, 'b': 0, 'c': -3, 'd': -6})Arithmetic and set operations
Counter supports four binary operators. Addition (+) and subtraction (-) combine counts; intersection (&) takes the minimum of each count and union (|) takes the maximum. A crucial rule: all four operators discard results that are zero or negative, so they always return a clean counter of positive counts. (Use subtract() instead when you need to keep negatives.)
c1 = Counter(a=3, b=1)
c2 = Counter(a=1, b=2, c=3)
c1 + c2 # Counter({'a': 4, 'b': 3, 'c': 3}) add counts
c1 - c2 # Counter({'a': 2}) keeps only positive (b, c dropped)
c1 & c2 # Counter({'a': 1, 'b': 1}) min of each
c1 | c2 # Counter({'a': 3, 'c': 3, 'b': 2}) max of eachThe unary + and - operators are a handy shorthand. +c returns a new counter with only the positive counts (a quick way to strip zeros and negatives after a subtract()), and -c flips signs and then keeps what is now positive.
counts = Counter(a=2, b=-4, c=0)
+counts # Counter({'a': 2}) keep only positive
-counts # Counter({'b': 4}) negate, then keep positivetotal(), and converting to and from a plain dict
Since Python 3.10, total() returns the sum of all counts (including any negatives) — previously you wrote sum(c.values()). Because Counter is a dict, converting to a plain dictionary is just dict(c), and you can sort items however you like with sorted().
c = Counter("aabbbc")
c.total() # 6 (Python 3.10+; same as sum(c.values()))
dict(c) # {'a': 2, 'b': 3, 'c': 1}
# Sort items by count (descending) -- most_common already does this:
sorted(c.items(), key=lambda kv: kv[1], reverse=True)
# [('b', 3), ('a', 2), ('c', 1)]
# Sort alphabetically by key instead:
sorted(c.items()) # [('a', 2), ('b', 3), ('c', 1)]Counter vs a manual dict vs defaultdict(int)
All three can count, but they differ in readability and built-in features. The table below summarises when to pick each; the code block shows the same character-frequency task written three ways.
| Approach | Counting code | Missing key | most_common / arithmetic |
Best for |
|---|---|---|---|---|
Manual dict + get |
d[ch] = d.get(ch, 0) + 1 |
returns your default | no — write it yourself | tiny scripts, no extra import |
defaultdict(int) |
d[ch] += 1 |
auto-creates 0 (mutates!) |
no — write it yourself | counting and grouping mixed in one structure |
Counter |
Counter(text) |
returns 0 (no mutation) |
built in | counting, ranking, and combining tallies |
from collections import Counter, defaultdict
text = "mississippi"
# 1. Manual dict + get -- explicit, no import needed
manual = {}
for ch in text:
manual[ch] = manual.get(ch, 0) + 1
# 2. defaultdict(int) -- clean loop, but reading a missing key inserts it
dd = defaultdict(int)
for ch in text:
dd[ch] += 1
# 3. Counter -- one line, plus most_common(), elements(), arithmetic
counts = Counter(text)
manual == dict(dd) == dict(counts) # True -- all three agree
counts.most_common(2) # [('i', 4), ('s', 4)] (only Counter gives this for free)Rule of thumb: if the job is purely counting and you want ranking or set-style combining, use Counter. If you are grouping values (lists, sets) and incidentally counting, defaultdict fits better. A manual dict is fine for a one-off in code that wants no imports. We cover defaultdict in depth in Part 1.
Real-world recipes
A few patterns cover most day-to-day uses of Counter: word-frequency analysis, tallying log events, anagram detection, and multiset (bag) arithmetic for things like inventory.
import re
from collections import Counter
# Word frequency from free text (normalise case, strip punctuation)
text = "Apple apple BANANA banana apple cherry"
freq = Counter(re.findall(r"[a-z]+", text.lower()))
freq.most_common() # [('apple', 3), ('banana', 2), ('cherry', 1)]
# Tally log levels and rank them
logs = ["INFO", "ERROR", "INFO", "WARN", "ERROR", "ERROR", "INFO"]
Counter(logs).most_common() # [('INFO', 3), ('ERROR', 3), ('WARN', 1)]from collections import Counter
# Anagram check: two strings are anagrams iff their letter counts match
def is_anagram(a, b):
return Counter(a) == Counter(b)
is_anagram("listen", "silent") # True
is_anagram("hello", "world") # False
# Multiset / bag arithmetic -- e.g. inventory and restock
cart = Counter(apple=3, pear=2)
restock = Counter(apple=5, banana=4)
cart + restock # Counter({'apple': 8, 'banana': 4, 'pear': 2})
sold = Counter(apple=1, pear=2)
cart - sold # Counter({'apple': 2}) (pear hits 0, dropped)Performance and gotchas
For pure counting, Counter(iterable) is typically as fast as or faster than a hand-written loop, because the tally happens in C rather than in Python bytecode. The win is largest when you build a counter directly from an iterable; an item-by-item counter[x] += 1 loop in Python is closer to a defaultdict(int) loop. Either way, prefer clarity — the speed difference rarely matters unless you are counting millions of items in a hot path.
Watch out for these:
- Arithmetic drops zero and negative counts.
c1 - c2,&,|, and unary+/-all keep only positive results. Usesubtract()when negatives must survive. - Zero does not delete a key. Setting
c[x] = 0(or reaching zero viasubtract()) leaves the key present with value0. Usedel c[x], or+cto strip non-positive entries. update()adds, it does not replace — the opposite ofdict.update().- Keys must be hashable.
Counter([[1], [2]])raisesTypeError; convert unhashable items (e.g. lists) to tuples first. - Iteration order is insertion order (a
dictguarantee since Python 3.7); onlymost_common()reorders by count. - A missing-key lookup returns 0 without inserting — safer than
defaultdict, which does insert on read.
Where to go next
Counter turns a whole category of fiddly counting code into a one-liner, and its most_common(), elements(), subtract(), and arithmetic operators handle ranking, expansion, and tally-combining without extra work. For the rest of the module — deque, namedtuple, defaultdict, OrderedDict, and ChainMap — start with Working with Python Collections — Part 1, and see our guide to generators and yield for another tool that keeps Python code lean.
At MicroPyramid we have shipped 50+ Python projects over 12+ years, where small idioms like reaching for the right container add up to faster, more maintainable code. If you are building or modernising a Python codebase, explore our Python development services, FastAPI development, and broader custom software development.
Frequently Asked Questions
What is collections.Counter used for?
Counter is a dict subclass for counting hashable objects. You pass it an iterable, mapping, or keyword arguments, and it produces a mapping of each element to its count. It is ideal for word and character frequencies, tallying log levels or event types, finding the top-N items with most_common(), treating a collection as a multiset/bag, and combining tallies with arithmetic operators.
Does accessing a missing key in a Counter raise KeyError?
No. Counter overrides __missing__ to return 0 for any element that was never counted, so c["never_seen"] is 0 rather than an error. Crucially, that lookup does not insert the key — unlike defaultdict, which creates the key on read. This makes a Counter safe to query without accidentally growing it.
How is Counter.update() different from dict.update()?
dict.update() replaces the value for each key, while Counter.update() adds the new counts to the existing ones. So Counter("ab").update("a") gives {'a': 2, 'b': 1}, not {'a': 1, 'b': 1}. The complementary subtract() method does the reverse and, unlike the - operator, keeps zero and negative results.
What is the difference between Counter and defaultdict(int)?
Both let you increment counts without a KeyError, but Counter is purpose-built: it constructs directly from an iterable (Counter(text)), returns 0 for missing keys without inserting them, and adds most_common(), elements(), subtract(), and arithmetic operators. Choose defaultdict when you are mainly grouping values (e.g. defaultdict(list)) and counting is secondary; choose Counter when counting, ranking, or combining tallies is the point.
How do I get the N most common or least common items?
Use most_common(n) for the n highest-count elements as (element, count) tuples, ordered from most to least frequent. Call most_common() with no argument to get all elements sorted by count. For the N least common, reverse the tail of the full list: counter.most_common()[:-n - 1:-1]. Ties are broken by first-insertion order, a guarantee since Python 3.7.
Why does Counter drop keys with zero or negative counts?
The arithmetic and set operators (+, -, &, |, and unary +/-) are defined to return only positive counts, so any element that nets to zero or below is removed from the result. This keeps combined tallies clean. When you need negatives to survive — for example tracking a deficit — use the in-place subtract() method, which preserves zero and negative values.