Overview

The collections module extends Python’s built-in types with specialized containers. Each type solves a specific pattern more cleanly than a plain dict or list. This card maps each collection to its primary use case and shows the API surface that matters in daily work. For type annotation patterns, see python-typing.

from collections import Counter, defaultdict, namedtuple, deque, ChainMap, OrderedDict

Counter

Count hashable objects. Built-in most_common, arithmetic, and set operations.

PatternCodeNotes
Count from iterableCounter("aababc"){'a': 3, 'b': 2, 'c': 1}
Count from dictCounter({'a': 3, 'b': 2})Accepts any mapping.
Most common Nc.most_common(3)Returns list of (element, count) tuples, descending.
Add countsc + Counter("ab")Returns new Counter; negative counts dropped.
Subtract countsc.subtract("aab")In-place; keeps zero and negative counts (unlike -).
Elementslist(c.elements())Expand back to iterable; repeats each element count times.
Missing keyc["missing"]Returns 0, not KeyError.
words = "the quick brown fox jumps over the lazy dog the fox".split()
freq = Counter(words)
print(freq.most_common(3))  # [('the', 3), ('fox', 2), ...]

Use Counter for word frequency, histogram bins, and vote tallies.

defaultdict

Dict that calls a factory for missing keys instead of raising KeyError.

PatternCodeNotes
List of values per keydefaultdict(list)d[key].append(v) without checking membership.
Count with intdefaultdict(int)d[key] += 1 without initialization.
Nested dictdefaultdict(dict)d[outer][inner] = v in one step.
Custom factorydefaultdict(lambda: "N/A")Any zero-arg callable.
Access without insertingd.get(key)d[key] inserts the default; .get() does not.
# Group words by first letter
from collections import defaultdict
groups = defaultdict(list)
for word in ["apple", "avocado", "banana", "blueberry", "cherry"]:
    groups[word[0]].append(word)
# {'a': ['apple', 'avocado'], 'b': ['banana', 'blueberry'], 'c': ['cherry']}

Prefer defaultdict(list) over setdefault in loops; it reads more clearly.

namedtuple and dataclass

namedtuple creates lightweight immutable record types. Use dataclass when you need mutability or methods.

APICodeNotes
DefinePoint = namedtuple("Point", ["x", "y"])Also accepts space-separated string "x y".
Instantiatep = Point(1, 2)Positional or keyword arguments.
Accessp.x, p[0]Both attribute and index access work.
As dictp._asdict()Returns dict; useful for serialization.
Replace a fieldp._replace(x=10)Returns new instance; original unchanged.
DefaultsPoint = namedtuple("Point", ["x", "y"], defaults=[0, 0])Python 3.6.1+.
Typed variantfrom typing import NamedTupleAllows per-field type hints.
from typing import NamedTuple
 
class Config(NamedTuple):
    host: str
    port: int = 5432
    ssl: bool = True
 
cfg = Config(host="localhost")

deque

Double-ended queue; O(1) append and pop from both ends. Use instead of a list when you need a sliding window or FIFO queue.

OperationCodeNotes
Create boundeddeque(maxlen=100)Old items auto-dropped when full.
Append rightd.append(v)Same as list.
Append leftd.appendleft(v)O(1); list insert(0, v) is O(n).
Pop rightd.pop()Raises IndexError if empty.
Pop leftd.popleft()O(1) FIFO dequeue.
Rotated.rotate(n)Right-rotate by n; negative rotates left.
Extend both endsd.extend(iterable), d.extendleft(iterable)extendleft reverses insertion order.
# Sliding window of last 5 log lines
log_tail = deque(maxlen=5)
for line in read_log():
    log_tail.append(line)

ChainMap

Treats multiple dicts as a single logical view. Lookups search dicts left to right; writes go to the first dict.

PatternCodeNotes
CreateChainMap(user_cfg, project_cfg, defaults)First dict has highest priority.
Add a layercm.new_child({"key": "val"})Returns new ChainMap; original unchanged.
Remove top layercm.parentsView without the first map.
Write throughcm["key"] = vWrites to the first dict only.
All mapscm.mapsList of underlying dicts in priority order.
import os
from collections import ChainMap
 
# Config priority: CLI args > env vars > defaults
defaults = {"debug": False, "port": 8000, "host": "0.0.0.0"}
env = {k.lower(): v for k, v in os.environ.items() if k in ("PORT", "HOST", "DEBUG")}
cli = {"port": 9000}  # from argparse
 
config = ChainMap(cli, env, defaults)
print(config["port"])  # 9000 (cli wins)

Common gotchas

  • Counter subtraction with - drops zero and negative counts silently. Use .subtract() when you need to keep those counts for further arithmetic.
  • defaultdict inserts the default value on key access (d[missing]). Iterating d.keys() after accidental reads produces unexpected entries. Use d.get(key) when you only want to check membership.
  • namedtuple fields are positional. Renaming a field in the definition breaks any code that uses positional construction. Prefer NamedTuple with keyword defaults for public APIs.
  • deque with maxlen does not raise on overflow; it silently drops the oldest item. If dropping data silently is wrong for your use case, check len(d) == d.maxlen before appending.
  • ChainMap writes go to the first dict. If the first dict is empty and you read then write the same key, the value is written to the empty dict and the original lower-priority dict is unchanged.
  • OrderedDict is rarely needed in Python 3.7+ because plain dict preserves insertion order. Use OrderedDict only when you need move_to_end or equality that respects order.