collections — specialized containers
The collections module provides container types that are faster or more ergonomic than list/dict for specific patterns.
defaultdict
A dict that auto-creates missing entries via a factory.
from collections import defaultdict
# Group by key
groups = defaultdict(list)
for word in words:
groups[word[0]].append(word)
# vs:
# groups = {}
# for word in words:
# groups.setdefault(word[0], []).append(word)
# Counting
counts = defaultdict(int)
for x in items:
counts[x] += 1
The factory is called with no arguments when a missing key is accessed. Use lambda for parameterized defaults: defaultdict(lambda: {"count": 0}).
Gotcha: d[k] creates the entry, even on read. To check without creating, use k in d first.
Counter
Multiset / frequency counter.
from collections import Counter
c = Counter("mississippi")
# Counter({'i': 4, 's': 4, 'p': 2, 'm': 1})
c.most_common(2) # [('i', 4), ('s', 4)]
c["x"] # 0 — missing keys return 0, no error
c.update("missing") # add another iterable's counts
c.subtract(other) # subtract counts (can go negative)
c1 + c2 # add counts (filters negatives)
c1 - c2 # subtract; negative results dropped
c1 & c2 # min of each (intersection)
c1 | c2 # max of each (union)
Common interview use: top-K elements, anagram check, duplicate detection.
deque — double-ended queue
O(1) appends and pops on both ends. Use for queues, sliding windows, and BFS.
from collections import deque
q = deque(maxlen=3) # bounded — pushes evict from the other end
q.append(1)
q.append(2)
q.appendleft(0) # [0, 1, 2]
q.popleft() # 0
q.pop() # 2
q.rotate(1) # rotate right by 1
deque(maxlen=N) is the standard “last N items” pattern — used for rate limiting windows, recent-events buffers.
OrderedDict
Since Python 3.7 regular dicts preserve order, so OrderedDict is mostly redundant. Two reasons it still exists:
move_to_end(key, last=True)— explicit reordering. Useful for LRU caches.==compares order:OrderedDict([('a', 1), ('b', 2)]) != OrderedDict([('b', 2), ('a', 1)])(regular dicts compare equal regardless of order).
from collections import OrderedDict
class LRU(OrderedDict):
def __init__(self, capacity):
self.capacity = capacity
def get(self, key):
if key in self:
self.move_to_end(key)
return self[key]
return None
def put(self, key, value):
if key in self:
self.move_to_end(key)
self[key] = value
if len(self) > self.capacity:
self.popitem(last=False) # evict oldest
namedtuple
Lightweight immutable record type. Mostly superseded by dataclass/NamedTuple (typed).
from collections import namedtuple
Point = namedtuple("Point", ["x", "y"])
p = Point(1, 2)
p.x, p.y # 1, 2
p[0] # also indexable like a tuple
p._asdict() # {'x': 1, 'y': 2}
p._replace(x=10) # new Point with x=10 (immutable, returns copy)
Modern alternative — typed:
from typing import NamedTuple
class Point(NamedTuple):
x: int
y: int
ChainMap — searches multiple dicts as one
from collections import ChainMap
defaults = {"timeout": 30, "retries": 3}
overrides = {"timeout": 60}
config = ChainMap(overrides, defaults)
config["timeout"] # 60 — first dict wins
config["retries"] # 3 — falls through to defaults
Useful for layered config (CLI → env → file → defaults). Mutations affect the first dict by default.
When to reach for what
| Need | Use |
|---|---|
| Group/bucket by key | defaultdict(list) |
| Count occurrences | Counter |
| FIFO queue, BFS | deque |
| Sliding window of last N | deque(maxlen=N) |
| LRU cache | OrderedDict.move_to_end (or functools.lru_cache) |
| Layered configuration | ChainMap |
| Lightweight record | dataclass or NamedTuple |
Interview angle
“How would you find the most common word in a text?” → Counter(words).most_common(1). “Implement an LRU cache” → OrderedDict solution. “Why is deque better than list for a queue?” → O(1) vs O(n) on popleft.