How do Counter's +, -, & and | operators differ from Counter.update() and Counter.subtract()?
answer
- Two families, three differences
- One returns, the other mutates
- Multiset results cannot go negative
- Minimum and maximum, not sum
- update adds where dict.update replaces
basics
~20 sThe four operators are multiset algebra: they build a new Counter and discard any count that is not positive. Counter.update() and Counter.subtract() mutate in place, add or subtract counts key by key, and keep zeros and negatives.
solid answer
~40 s`+`, `-`, `&` and `|` treat two Counters as multisets: `+` sums counts, `-` subtracts, `&` takes the per-key minimum, `|` the per-key maximum — and each **returns a new Counter with every non-positive result dropped**, so `Counter(a=3, b=1) - Counter(a=1, b=2)` is `Counter({'a': 2})`, with `b` gone rather than `-1`. `Counter.update()` and `Counter.subtract()` are the in-place, arithmetic-faithful pair: they mutate the receiver, accept any iterable or mapping (not just a Counter), and **keep** zero and negative counts, so the same subtraction via `subtract()` leaves `b: -1`. Note also that `Counter.update()` adds counts where `dict.update()` would replace values. Reach for the operators when you want a clean positive result, for `subtract()` when the deficit itself is the information you need.
code
python · 12 linesfrom collections import Counter
inv = Counter(a=3, b=1)
sold = Counter(a=1, b=2)
print(inv + sold) # Counter({'a': 4, 'b': 3})
print(inv - sold) # Counter({'a': 2})
print(inv & sold) # Counter({'a': 1, 'b': 1})
print(inv | sold) # Counter({'a': 3, 'b': 2})
inv.subtract(sold)
print(inv) # Counter({'a': 2, 'b': -1})go deeper
Know that Counters can be combined with arithmetic at all, and that + gives you back a new Counter rather than changing either input. Being able to name update() as the in-place add is enough here.
Explain all three differences — new object vs in-place, non-positive counts dropped vs kept, Counter-only vs any iterable — and show the one example where - and subtract() disagree. Mention that update() adds where dict.update() replaces.
Argue the choice from the domain: when is a negative count real information rather than noise? Show that you strip with unary + before comparing, and that you merge many counters with update() instead of chained + to avoid intermediates.
Own the semantic decision at the API boundary: a shortfall silently pruned to absence is a data-loss bug that looks like clean data. Decide once, per system, whether counts are multiset-shaped or ledger-shaped, and encode it in the type callers see.
## Two families of combination `collections.Counter` gives you two ways to combine counts, and they differ on three axes at once: **new object vs in-place**, **non-positive counts dropped vs kept**, and **Counter-only vs any iterable or mapping**. Confusing them is one of the most common real bugs in counting code. ### The operator family: multiset algebra ```python from collections import Counter inv = Counter(a=3, b=1) sold = Counter(a=1, b=2) inv + sold # Counter({'a': 4, 'b': 3}) sum inv - sold # Counter({'a': 2}) difference, b dropped inv & sold # Counter({'a': 1, 'b': 1}) per-key minimum (intersection) inv | sold # Counter({'a': 3, 'b': 2}) per-key maximum (union) ``` All four: - return a **new** Counter and leave both operands untouched; - compute per key, treating a key absent from one side as count 0; - **omit any key whose result is zero or negative**. That is the multiset rule — a multiset cannot contain minus one of something — and it is why `b` vanishes from `inv - sold` instead of appearing as `-1`. They also require both operands to be Counters. `Counter(a=1) + {"a": 1}` raises `TypeError`; the operator returns `NotImplemented` for a non-Counter and nothing else handles it. Wrap the dict — `c + Counter(d)` — or use `update()`. There are two unary operators in the same family: `+c` returns a new Counter holding only the positive counts (the documented way to strip zeros and negatives after a `subtract()`), and `-c` returns the negated counts that were negative. ### The method family: in-place arithmetic ```python inv = Counter(a=3, b=1) inv.subtract(Counter(a=1, b=2)) inv # Counter({'a': 2, 'b': -1}) ``` `Counter.update()` and `Counter.subtract()` mutate the receiver and return `None`. They keep the arithmetic exactly as computed: zeros stay, negatives stay. And they are liberal in what they accept — another Counter, a plain mapping of counts, or a bare iterable of elements: ```python c = Counter() c.update("aab") # iterable of elements -> Counter({'a': 2, 'b': 1}) c.update({"a": 10}) # mapping of counts -> Counter({'a': 12, 'b': 1}) c.subtract(["a"]) # iterable again -> Counter({'a': 11, 'b': 1}) ``` The `update()` behaviour is worth calling out on its own, because it silently overrides the inherited one: `dict.update()` **replaces** a value, `Counter.update()` **adds** to it. Someone reading `c.update({"a": 10})` as a dict operation will expect `a == 10` and get `a == 12`. ## Choosing between them The question behind the question is: *is a deficit meaningful?* - If you are computing "what is left over", "what do both have in common" or "what is the merged total", the operators are right — the non-positive pruning is the semantics you want, and the fresh object keeps the inputs intact. - If you are tracking a running balance, a drift, or an outstanding shortfall, `subtract()` is right, because the negative count **is** the answer. Dropping it silently turns "we are two short" into "we have none of these", which reads the same as "we never had any". - If your right-hand side is a raw stream of elements rather than a Counter, only `update()` and `subtract()` will take it. There is a performance dimension too: `update()` mutates one object, while chaining `total = a + b + c + d` allocates an intermediate Counter at every step. For a merge over many partial counters, `for part in parts: total.update(part)` avoids that garbage. ## A worked example of the difference mattering Reconciling counted stock against a shipment manifest is the canonical case. `on_hand - shipped` answers "what is still on the shelf", and the pruning is right: an item you are short of is not on the shelf in a negative quantity, it is simply not there. But `on_hand.subtract(shipped)` answers a different question — "where are we out of balance" — and only that form can report the shortfall. Write the first when you are producing a picture of reality, the second when you are producing a discrepancy report. Choosing the operator because it reads more nicely, and then wondering why shortages never appear in the output, is a bug that survives code review precisely because the code looks clean. ## The comparison operators Since Python 3.10, Counters also support `<`, `<=`, `>` and `>=` as multiset containment tests: `Counter(a=1) <= Counter(a=2, b=1)` is `True`, meaning every count on the left is at most the matching count on the right. `==` remains ordinary dict equality, so a zero-count key makes two Counters unequal even though they describe the same multiset — another reason to strip with unary `+` before comparing. ## What to say in an interview Give the three axes — new vs in-place, drops non-positive vs keeps it, Counter-only vs any iterable — then show the one-line example where `-` and `subtract()` disagree. Adding that `Counter.update()` adds where `dict.update()` replaces usually ends the topic.
- What happens if you write `Counter(a=1) + {"a": 1}`?It raises `TypeError`. The operator returns `NotImplemented` unless the other operand is also a Counter, and nothing else supplies the operation. Either wrap the mapping — `c + Counter(d)` — or use `c.update(d)`, which accepts any mapping or iterable.
- How do you clear the zeros and negatives left behind by Counter.subtract()?Apply unary `+`: `c = +c` returns a new Counter containing only the counts greater than zero. That is the documented cleanup idiom, and it matters before equality comparisons, because `==` is plain dict equality and a lingering `'b': 0` makes two otherwise-identical Counters compare unequal.
- Merging many partial Counters: why prefer update() over chained `+`?Chaining `a + b + c` allocates a fresh Counter at every step and copies the accumulated keys each time. Looping `for part in parts: total.update(part)` mutates one object and touches each key once. Use the operators when you need the pruning semantics or must not mutate the inputs.
saying these in an interview costs you the question
- Thinks Counter.update replaces counts like dict.update
- Expects `-` to leave negative counts in the result
- Says `&` sums counts and `|` intersects keys
- Believes `+` mutates the left operand in place
- Assumes the operators accept a plain dict
- Uses `-` for balances where the deficit is the answer