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

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"])             # 0
from 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"])   # 6

Note 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

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

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.