backend / python core / stdlib / 01_collections.md

collections — specialized containers

3 min read source

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:

  1. move_to_end(key, last=True) — explicit reordering. Useful for LRU caches.
  2. == 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.