AlgoMaster Logo

Counter

High Priority25 min readUpdated June 6, 2026
Listen to this chapter
Unlock Audio

Counter is a dictionary subclass from the collections module built for one job: counting how often things appear. You hand it an iterable of items and it hands back a dict-like object mapping each item to its count, with arithmetic operators and a handful of methods that turn the usual "how many of each" loops into one-liners. This lesson covers the four ways to construct one, the quirks that make it different from a plain dict, and the operators you'll actually use in shopping-cart and inventory code.

What Counter Actually Is

Strip away the helpers and a Counter is a dict where every value is an integer count. The keys are the things you're counting (product names, categories, customer IDs, anything hashable) and the values are how many times each one showed up.

Because Counter is a dict subclass, every dict operation works: counts["apple"], for item in counts, counts.items(), len(counts), the whole interface. What it adds is a counting-aware constructor, methods like most_common, and arithmetic operators that combine counters element by element.

You might wonder why we don't just use defaultdict(int) for this, since that also lets you write counts[item] += 1 without a KeyError. The difference is that defaultdict(int) only solves the "missing key" problem. It doesn't have most_common, it doesn't add or subtract counts, and it has no notion of "items repeated by count". Counter is the specialized tool for this job, and the helpers are what make it worth importing.

Four Ways to Construct a Counter

A Counter can be built from an iterable, from a mapping, from keyword arguments, or empty. All four end up as the same kind of object.

From an Iterable

The most common form. Pass any iterable of hashable items and Counter tallies them.

The iterable can be a list, a tuple, a string (which counts characters), a generator, or anything else you can loop over.

That's almost never what you want for reviews; you usually want to count words, not characters. Split first.

From a Mapping

Pass a dict (or another Counter) and the values become the counts directly.

This is the form to use when you already have aggregated numbers, like an inventory snapshot from a database, rather than a list of individual items to tally.

From Keyword Arguments

For small, hard-coded counters, keyword arguments are concise.

The keys are limited to valid Python identifiers in this form, so you can't use names with spaces or dashes. For arbitrary keys, use the mapping form.

Empty

Counter() with no arguments gives you an empty counter ready to be filled later. This is the natural starting point for tally loops.

In practice you'd often skip the loop and write Counter(item for order in orders for item in order), but the empty-then-update pattern is useful when the counting logic is more involved than a single iterable.

Missing Keys Return Zero, Not KeyError

This is the first behavior that sets Counter apart from a regular dict. Looking up a key that was never counted returns 0 instead of raising.

A plain dict would raise KeyError on sales["pineapple"]. Counter returns 0, which is the right answer to "how many pineapples did we sell?" when none were sold.

The subtle part: the missing key is not added to the counter. Looking it up returns 0 but doesn't store anything.

sales["pineapple"] returned 0, but "pineapple" in sales is still False and iterating over the counter only yields the keys that were actually counted. Compare this to defaultdict(int), where looking up a missing key does insert it with a default value of 0.

defaultdict inserts on lookup; Counter does not. If you're doing thousands of "did we see this item" checks against a Counter, you won't accidentally bloat it with empty entries.

Assignment still works the way you'd expect: sales["pineapple"] = 1 does add the key, because that's an explicit assignment rather than a lookup.

most_common(n)

Counters know how to sort themselves by count. most_common(n) returns the top n items as a list of (item, count) tuples in descending order.

Ties are broken by insertion order. apple and milk are both at 4, and apple was counted first, so it comes first in the result.

Called with no argument, most_common() returns every item sorted by count.

To get the least common items, you can either reverse the full list or pass a negative slice.

most_common is the method you'll reach for most often. Best-selling products, top searches, top reviewers, busiest categories, all variations of the same shape.

Arithmetic Operators on Counters

Two counters can be combined with +, -, &, and |. Each operator runs element by element, treating missing keys as zero. The result is always a new counter; the originals don't change.

+ Adds Counts

Every key from either counter shows up in the result with its summed count. Keys that only appear on one side keep their original count (because the other side is treated as zero).

There's a small twist: + drops items whose result is zero or negative. This matters for + on counters that can have negative counts (you can construct such counters via subtract, shown below).

banana ends up at -1 + 1 = 0, so it's dropped from the result. This is the "useful counting" convention: + on counters cleans up to positive counts only.

- Subtracts Counts

Subtraction shows what's still pending. banana was fully shipped (3 - 3 = 0) so it doesn't appear in the result. Like +, the - operator drops zero and negative results, which is what you want for "what's left to do" semantics.

If you want a subtraction that keeps negative counts, use the subtract method instead. We'll see it shortly.

& Intersection (Min)

The & operator takes the minimum count of each key. Think of it as "what's in both".

For each item, you get the smaller of the two counts. apple is min(5, 2) = 2, banana is min(3, 10) = 3. milk doesn't appear in available, so its min is 0 and it's dropped. bread doesn't appear in needed, same reason.

A practical use: how many of each item can we actually fulfill given what's needed and what's in stock?

| Union (Max)

The | operator takes the maximum count of each key.

For each item, you get the larger count. This is "the best we've ever had of each item" semantics.

Unary + and -

Unary +counter keeps only positive counts. Unary -counter flips signs and keeps only what was originally negative.

+mixed keeps apple (positive), drops banana (negative), drops milk (zero). -mixed flips signs and keeps only the result that's positive after flipping, which gives banana=2.

You'll mostly see unary + after a subtract call, as a tidy-up step that throws away the negatives.

update Adds Counts, Doesn't Replace

This is one of the easiest places to get burned. A plain dict.update(other) replaces values for any matching keys. Counter.update(other) adds to them. Same method name, very different behavior.

Watch what happened: apple went from 3 to 4, not from 3 to 1. The 1 was added to the existing count. milk was new, so it landed at its given value of 4. banana wasn't mentioned in the update, so it stayed at 2.

If you had used a plain dict's update behavior in your head, you'd be expecting apple to be 1 after this call. That's the bug. In Counter, update means "merge counts".

update accepts an iterable too, and tallies it the same way the constructor does, then adds those tallies to the existing counts. This is how you grow a counter over time without rebuilding it from scratch.

subtract: Like update, but Subtracts

subtract mirrors update, but subtracts the given counts. Unlike the - operator, it allows negative counts in the result.

Notice milk ended up at -1. We sold more than we had in inventory, and subtract records that honestly. The - operator would have hidden it; subtract shows the discrepancy, which is often what you want when reconciling expected vs actual counts.

If you want positive counts only after subtracting, follow up with unary +:

banana (zero) and milk (negative) are gone, leaving only items still in stock.

elements(): Counts Back to Items

elements() is the inverse of the iterable constructor. It returns an iterator that yields each item repeated by its count, in insertion order. Items with a count of zero or less are skipped.

This is useful when something downstream wants the original "stream of items" representation. For example, sampling proportionally, or feeding into a function that expects a list.

Negative and zero counts are silently dropped. banana and milk don't make it into the output.

total(): Sum of All Counts

total() was added in Python 3.10. It returns the sum of all counts in the counter, including negative ones.

Before 3.10, the equivalent was sum(cart.values()), which still works and is portable across older versions.

total() and sum(c.values()) agree, even when some counts are negative.

Method and Operator Reference

A consolidated view of what Counter ships with, beyond what dict already provides:

ItemWhat it doesNotes
Counter(iterable)Tallies items from an iterableEach element must be hashable
Counter(mapping)Uses mapping values as countsValues should be integers
Counter(**kwargs)Tallies from keyword argsKeys limited to identifiers
c[missing]Returns 0, does not insertCompare to defaultdict(int) which inserts
c.most_common(n)Top n (item, count) tuplesTies broken by insertion order
c.most_common()All items, sorted by countReturns a list
c.update(other)Adds counts (does NOT replace)Easy to confuse with dict.update
c.subtract(other)Subtracts countsAllows negative counts
c.elements()Iterator of items repeated by countSkips zero and negative
c.total()Sum of all countsPython 3.10+
a + bAdd countsDrops zero/negative results
a - bSubtract countsDrops zero/negative results
a & bMin of each count (intersection)Keys present in both
`ab`Max of each count (union)
+cKeep only positive countsUseful after subtract
-cKeep absolute values of negativesInverse of +c

Common Patterns

A few shapes show up in real e-commerce code often enough to be worth seeing explicitly. Each one would be a small loop in plain Python; with Counter it's a couple of lines.

Best-Selling Products

Restocking by Category

Count how many items in each category fall below a stock threshold.

Two electronics products are low, one cable, one kitchen item. The generator expression filters and projects in one pass, and Counter handles the tally.

Comparing Two Shopping Carts

Subtraction is the cleanest way to ask "what does cart A have that cart B doesn't?"

The customer dropped one apple and the bread; they didn't add anything new. Two subtractions, each in one direction, capture both sides of the diff.

Anagram Check

Two strings are anagrams when their character counts match.

Counter equality is dict equality: same keys, same values, in any order. No need to sort the strings or write a frequency-comparison loop by hand.

How Counter Fits With the Rest

The places Counter overlaps with other dict-like types are worth naming, just so you reach for the right tool.

Counter is the right choice the moment you find yourself writing a loop that does counts[item] = counts.get(item, 0) + 1 or its defaultdict(int) equivalent. The constructor does that loop for you, and the helpers (most_common, the arithmetic operators, elements, total) cover the next several things you'd otherwise have to write by hand.

Quiz

Counter Quiz

10 quizzes