The Quiet Terminal

← Practical Tips

Four containers from collections that earn their keep

2026-04-24 · 4 min read

I ignored collections for years because the name sounds like the module equivalent of a junk drawer. It's the opposite: a shelf of containers that each replace ten lines of hand-rolled bookkeeping with three lines that say what they mean. These are the four I actually use.

Counter

from collections import Counter

words = "spam eggs spam bacon spam eggs".split()
counts = Counter(words)
counts.most_common(1)    # [('spam', 3)]

Reach for it when you're about to write the manual count loop — the one with if word in counts: in it. Counter just counts, and most_common(n) gives you the leaderboard for free. It's a dict underneath, so counts["spam"] works, and asking for a key that never appeared returns 0 instead of raising.

defaultdict

from collections import defaultdict

by_ext = defaultdict(list)
for name in ["a.py", "b.txt", "c.py"]:
    by_ext[name.split(".")[-1]].append(name)
# {'py': ['a.py', 'c.py'], 'txt': ['b.txt']}

Reach for it when you're grouping things: a missing key creates an empty list instead of raising KeyError. My hand-written version of this loop had an if ext not in by_ext: branch and still crashed on the one filename without a dot in it.

deque

from collections import deque

window = deque(maxlen=3)
for n in [4, 8, 15, 16, 23]:
    window.append(n)
# deque([15, 16, 23])

With maxlen set, appending to a full deque silently drops the oldest item — a rolling window in two lines, which is how I keep "the last N lines of the log" and how I'd tail a growing file without it growing in memory. It's also the queue container: popleft() is O(1), while list.pop(0) shifts every remaining element. Reach for it whenever the front of the line matters as much as the back.

namedtuple

from collections import namedtuple

Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)
x, y = p                 # unpacks like a tuple
print(p)                 # Point(x=3, y=4)

Reach for it when a dict always has the same three keys and you're tired of row["lat"] and the one typo that comes with it. You get attribute access, unpacking, and a readable repr, and the objects are immutable — nobody downstream can rename a field by accident.

They compose

Real scripts rarely use just one. Tallying what's in a folder, for instance:

from pathlib import Path
from collections import Counter

exts = Counter(f.suffix.lower() for f in Path(".").iterdir() if f.is_file())
print(exts.most_common(3))

A Counter fed by a Path generator answers in two lines what I used to answer by opening the folder and squinting. If you're unsure which to reach for: Counter for counting, defaultdict for grouping, deque when order matters, namedtuple for rows.

The test I use for whether something in the standard library earns its keep: did it delete code, or just relabel it? Every one of these four deleted code I had genuinely written by hand, badly. That's the whole recommendation.