collections Module
The collections module extends built-in containers with specialized types tuned for common patterns - grouping, counting, double-ended queues, and layered configuration.
Recipe
from collections import Counter, defaultdict, deque
words = Counter("abracadabra".split())
by_len: defaultdict[int, list[str]] = defaultdict(list)
queue: deque[str] = deque(maxlen=100)When to reach for this:
- Grouping items by key without
if key not in dict - Frequency analysis and top-N counts
- BFS queues and rolling buffers
- Stacked config overlays with
ChainMap
Working Example
from collections import ChainMap, Counter, defaultdict, deque
def group_by(items: list[dict], field: str) -> dict[str, list[dict]]:
groups: defaultdict[str, list[dict]] = defaultdict(list)
for item in items:
groups[str(item[field])].append(item)
return dict(groups)
def top_n_words(text: str, n: int) -> list[tuple[str, int]]:
counts = Counter(word.lower() for word in text.split())
return counts.most_common(n)
def sliding_window(values: list[int], size: int) -> list[int]:
window: deque[int] = deque(maxlen=size)
sums: list[int] = []
for v in values:
window.append(v)
if len(window) == size:
sums.append(sum(window))
return sums
def merged_config(*maps: dict[str, object]) -> ChainMap:
return ChainMap(*maps)
if __name__ == "__main__":
rows = [{"role": "dev", "name": "a"}, {"role": "dev", "name": "b"}]
print(group_by(rows, "role"))
print(top_n_words("a b a c a b", 2))
print(sliding_window([1, 2, 3, 4, 5], 3))What this demonstrates:
defaultdict(list)appends without existence checksCounter.most_commonreturns ranked pairsdeque(maxlen=...)auto-evicts oldest for rolling windowsChainMapsearches dict stack for first matching key
Deep Dive
How It Works
- defaultdict - Subclasses dict; factory callable on
__missing__. - Counter - Dict subclass; missing keys return 0; supports
+,-,&,|. - deque - Doubly-linked blocks; O(1) append/pop both ends.
- OrderedDict - Historically ordered dict; now mostly legacy except
move_to_end. - ChainMap - Non-destructive overlay; writes go to first map only.
Pick the Right Type
| Type | Use |
|---|---|
deque | Queue, stack, rolling window |
Counter | Word counts, histograms |
defaultdict | Group-by, adjacency lists |
ChainMap | Scoped settings layers |
Python Notes
# Counter as multiset
c1 = Counter(a=3, b=1)
c2 = Counter(a=1, c=2)
print(c1 + c2)
# move_to_end on OrderedDict for LRU-ish orderingGotchas
- defaultdict factory side effects - Factory called only on missing key - still avoid heavy work in factory. Fix: Use simple
list,int,set. - Counter negative counts - Allowed but confusing. Fix: Use
subtractknowingly or filter positives. - deque maxlen silent drop - Oldest items vanish without error. Fix: Document behavior or check length.
- ChainMap write surprises - Updates only first dict. Fix: Copy merged result if you need flat dict.
- OrderedDict for ordering - Plain dict already ordered. Fix: Use OrderedDict only for
move_to_endLRU patterns.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
plain dict | Simple maps | Repetitive missing-key boilerplate |
pandas.value_counts | DataFrame columns | Stdlib-only script |
heapq | Priority ordering | FIFO queue only |
functools.lru_cache | Function memoization | Counting arbitrary iterables |
FAQs
Is OrderedDict obsolete?
Mostly - regular dict preserves insert order. Keep OrderedDict for move_to_end LRU behaviors.
defaultdict or setdefault?
defaultdict cleaner for accumulation. setdefault fine for occasional missing keys.
Can Counter count objects?
Yes if hashable. Often count strings or tuples extracted from records.
deque thread-safe?
Append/pop thread-safe in CPython due to GIL; still coordinate compound operations across threads.
How ChainMap for env config?
ChainMap(os.environ, defaults) - env overrides defaults without copying.
most_common big-O?
Uses heap for top-n - efficient when n << unique keys.
namedtuple in collections?
Moved toward typing.NamedTuple and dataclasses - still available as collections.namedtuple.
Counter elements()?
Expands counts to repeated items - useful for re-feeding multiset into algorithms.
deque vs list as queue?
Never list.pop(0) in hot loops - O(n). Always deque.popleft().
Can I serialize defaultdict?
Convert to plain dict first: dict(dd) - factory not preserved in JSON naturally.
Related
- Dictionaries - underlying map semantics
- heapq & Priority Queues - ordering
- Lists & Tuples - when deque beats list
- Choosing the Right Data Structure - decision guide
Stack versions: This page was written for Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.