Pythonโs collections Module: The Data Structures the Standard Library Hides
Pythonโs built-in list, dict, set, and tuple cover most situations. The collections module extends these with specialised types that solve specific patterns more cleanly. If you find yourself writing boilerplate code to handle missing keys, count occurrences, or maintain insertion order, there is likely a collections type that makes it unnecessary.
Counter โ Frequency Counting in One Call
Counter takes any iterable and counts how often each element appears.
from collections import Counter
words = ["apple", "banana", "apple", "cherry", "banana", "apple"]freq = Counter(words)print(freq)# Counter({'apple': 3, 'banana': 2, 'cherry': 1})
# Most common elementsprint(freq.most_common(2))# [('apple', 3), ('banana', 2)]
# Counter arithmetica = Counter(["x", "x", "y"])b = Counter(["x", "z"])print(a + b) # Counter({'x': 3, 'y': 1, 'z': 1})print(a - b) # Counter({'x': 1, 'y': 1}) โ subtracts, drops zeros/negativesWithout Counter, you would write a loop, initialise counts to zero, and handle the KeyError on the first occurrence. Counter handles all of that.
defaultdict โ No More Missing Key Checks
A defaultdict calls a factory function to supply a default value whenever a key does not exist. No more if key not in d: d[key] = [].
from collections import defaultdict
# Group words by their first letterwords = ["ant", "bear", "cat", "alligator", "buffalo", "crane"]by_letter = defaultdict(list)
for word in words: by_letter[word[0]].append(word) # no KeyError โ missing key creates []
print(dict(by_letter))# {'a': ['ant', 'alligator'], 'b': ['bear', 'buffalo'], 'c': ['cat', 'crane']}
# Counting with defaultdict(int)sentence = "the cat sat on the mat"word_count = defaultdict(int)for word in sentence.split(): word_count[word] += 1 # missing key starts at 0
print(dict(word_count))# {'the': 2, 'cat': 1, 'sat': 1, 'on': 1, 'mat': 1}deque โ O(1) Appends and Pops from Both Ends
A deque (double-ended queue) supports O(1) appends and pops from either end. list.pop(0) is O(n) because it shifts every element. deque.popleft() is O(1).
from collections import deque
# Queue (FIFO) โ first in, first outqueue = deque()queue.append("task1")queue.append("task2")queue.append("task3")print(queue.popleft()) # "task1" โ O(1)
# Bounded recent historyrecent = deque(maxlen=3)for item in range(6): recent.append(item) print(list(recent))# Keeps only the 3 most recent itemsnamedtuple โ Readable Tuples with Named Fields
namedtuple creates a tuple subclass whose elements have names. You get the immutability and memory efficiency of a tuple, plus the readability of attribute access.
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])p = Point(3, 4)print(p.x, p.y) # 3 4print(p[0], p[1]) # 3 4 โ still works as a regular tupleprint(p._asdict()) # OrderedDict([('x', 3), ('y', 4)])OrderedDict โ When Dict Order Must Be Explicit
Since Python 3.7, regular dicts maintain insertion order. OrderedDict is useful when the order itself is meaningful, or when you need move_to_end():
from collections import OrderedDict
od = OrderedDict()od['first'] = 1od['second'] = 2od['third'] = 3
od.move_to_end('first') # move 'first' to the endprint(list(od.keys())) # ['second', 'third', 'first']
# LRU cache-like pattern: evict the least-recently-used itemod.move_to_end('second', last=False) # move to frontprint(list(od.keys())) # ['second', 'third', 'first']ChainMap โ Multiple Dicts as One View
ChainMap combines multiple dicts into a single view. Lookups search each dict in order without merging them. Writes go to the first dict only.
from collections import ChainMap
defaults = {"theme": "light", "font_size": 12, "debug": False}user_prefs = {"theme": "dark"}cli_args = {"debug": True}
config = ChainMap(cli_args, user_prefs, defaults)print(config["theme"]) # "dark" โ from user_prefsprint(config["font_size"]) # 12 โ from defaultsprint(config["debug"]) # True โ from cli_argsThis is the cleanest way to implement layered configuration (CLI overrides env overrides config file overrides defaults).
Quick Comparison Table
| Type | When to use |
|---|---|
Counter | Counting occurrences, frequency tables, word counts |
defaultdict | Grouping, accumulating into lists/sets per key |
deque | Queues, sliding windows, bounded history |
namedtuple | Lightweight immutable records with meaningful field names |
OrderedDict | When move_to_end() is needed, or order is semantically significant |
ChainMap | Layered configuration, variable scoping simulation |