The collections module in the Python standard library provides specialized container types that extend Python's built-in list, dict, tuple, and set. These containers handle common patterns like counting items, grouping values, managing queues, and creating lightweight record types, without writing the same boilerplate over and over.
from collections import Counter
words = ["apple", "banana", "apple", "cherry", "banana", "apple"]
counts = Counter(words)
print(counts) # Counter({'apple': 3, 'banana': 2, 'cherry': 1})
print(counts.most_common(2)) # [('apple', 3), ('banana', 2)]Counter(words) counts how many times each word appears in the list, building a dictionary-like mapping from word to count in a single call. .most_common(2) returns the two most frequent items, sorted by count, along with their totals.
Counter: counting hashable items
Counting occurrences by hand usually means a dict plus a few lines of boilerplate for missing keys. Counter is a dictionary subclass for counting, and you can pass it any iterable directly.
from collections import Counter
text = "mississippi"
counts = Counter(text)
print(counts) # Counter({'i': 4, 's': 4, 'p': 2, 'm': 1})
print(counts["i"]) # 4
print(counts["z"]) # 0Missing keys return 0 instead of raising KeyError, which is convenient in loops where you increment a count before knowing whether the key has been seen before. Counter instances also support arithmetic operators directly, similar to sets and vectors:
from collections import Counter
a = Counter("abracadabra")
b = Counter("alakazam")
print(a + b)
print(a - b)
print(a & b)
print(a | b)Each operator combines the two counters differently, from summing every count to keeping only the shared minimums, and the output below shows all four results side by side for the same pair of counters:
Counter({'a': 9, 'b': 2, 'r': 2, 'c': 1, 'd': 1, 'l': 1, 'k': 1, 'z': 1, 'm': 1})
Counter({'b': 2, 'r': 2, 'a': 1, 'c': 1, 'd': 1})
Counter({'a': 4})
Counter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1, 'l': 1, 'k': 1, 'z': 1, 'm': 1})- sums counts. - subtracts (drops non-positive results). & takes minimums (intersection). | takes maximums (union).
defaultdict: automatic default values
Grouping items by key normally means checking whether the key already exists before appending to it. defaultdict returns a default value when you access a missing key, instead of raising KeyError, which removes that check entirely.
from collections import defaultdict
students = [("Maya", "A"), ("Devin", "B"), ("Maya", "B"), ("Priya", "A")]
grades = defaultdict(list)
for name, grade in students:
grades[name].append(grade)
print(dict(grades)) # {'Maya': ['A', 'B'], 'Devin': ['B'], 'Priya': ['A']}defaultdict(list) creates a new empty list for every new key. No if name not in grades check is needed. Pass any callable that takes no arguments:
counts = defaultdict(int)
positions = defaultdict(lambda: (0, 0))defaultdict(int) returns 0 for missing keys. defaultdict(lambda: (0, 0)) returns a tuple for missing keys.
deque: fast double-ended queue
deque (pronounced "deck") supports fast appends and pops from both ends. It is ideal for queues, stacks, and sliding windows.
from collections import deque
history = deque(maxlen=3)
history.append("first")
history.append("second")
history.append("third")
history.append("fourth")
print(history) # deque(['second', 'third', 'fourth'], maxlen=3)maxlen=3 keeps only the last three items. When a fourth is added, the oldest is automatically removed. This is perfect for log buffers, recent-item lists, and sliding window calculations.
Operations at both ends are O(1), which is the whole reason deque exists instead of just using a plain list for this kind of work:
from collections import deque
queue = deque(["a", "b", "c"])
queue.append("d")
queue.appendleft("z")
print(queue)
print(queue.pop())
print(queue.popleft())
print(queue)The print after the appends shows the new order, and the two pops remove one item from each end before the final print confirms what remains:
deque(['z', 'a', 'b', 'c', 'd'])
d
z
deque(['a', 'b', 'c'])Use deque instead of list when you add or remove from the beginning. list.pop(0) is O(n); deque.popleft() is O(1).
namedtuple: lightweight record types
namedtuple creates tuple subclasses with named fields. They are immutable, memory-efficient, and support both attribute and index access.
from collections import namedtuple
Point = namedtuple("Point", ["x", "y"])
p = Point(10, 20)
print(p.x, p.y) # 10 20
print(p[0], p[1]) # 10 20
x, y = p
print(x, y) # 10 20Attribute access, index access, and unpacking all return the same values, since a namedtuple is still a plain tuple underneath. Named tuples work everywhere tuples work: indexing, unpacking, and hashing. They are great for representing coordinates, database rows, configuration records, and any structured data that should not change after creation.
from collections import namedtuple
Student = namedtuple("Student", ["name", "score", "grade"])
records = [
Student("Maya", 92, "A"),
Student("Devin", 85, "B"),
]
for s in records:
print(f"{s.name}: {s.score}")The loop prints one line per student, reading each field by name instead of by tuple position, which keeps the code readable even as more fields get added later:
Maya: 92
Devin: 85OrderedDict: order-preserving dictionary
Since Python 3.7, regular dict maintains insertion order. OrderedDict still has value for specific use cases like LRU caches and reordering operations.
from collections import OrderedDict
cache = OrderedDict([("a", 1), ("b", 2), ("c", 3)])
cache.move_to_end("a")
print(cache)
cache.popitem(last=False)
print(cache)The first print shows "a" moved to the end after move_to_end(), and the second print shows the dict after popitem(last=False) removes whichever key is now first:
OrderedDict({'b': 2, 'c': 3, 'a': 1})
OrderedDict({'c': 3, 'a': 1})move_to_end() moves a key to the front or back. popitem(last=False) removes the first item. These operations make OrderedDict useful for implementing LRU caches and ordered sets.
ChainMap: group multiple dictionaries
ChainMap groups multiple dictionaries into a single mapping. Lookups search each dictionary in order.
from collections import ChainMap
defaults = {"theme": "light", "font_size": 14, "language": "en"}
user = {"theme": "dark", "font_size": 16}
settings = ChainMap(user, defaults)
print(settings["theme"])
print(settings["font_size"])
print(settings["language"])theme and font_size resolve from user since it comes first in the ChainMap, while language falls through to defaults since user does not define it:
dark
16
ensettings["theme"] finds the value in user first because it is the first mapping. settings["language"] falls through to defaults. This pattern is useful for configuration layering, where command-line arguments override config files, which override defaults.
Comparison of container types
| Type | Use case | Mutability |
|---|---|---|
| Counter | Count items, find most common | Mutable |
| defaultdict | Dict with automatic default values | Mutable |
| deque | Fast queue/stack at both ends | Mutable |
| namedtuple | Lightweight immutable records | Immutable |
| OrderedDict | Dict with reordering operations | Mutable |
| ChainMap | Layered lookup across dicts | Mutable (underlying dicts) |
Common mistakes
Using list as a queue. list.pop(0) shifts all remaining elements, making it O(n). Use deque with .popleft() for O(1) queue behavior.
Using Counter for arithmetic without checking the result. Counter subtraction drops non-positive counts. If you need negative counts, use a regular defaultdict(int) instead.
Confusing namedtuple with dataclass. namedtuple is immutable, lighter, and tuple-compatible. dataclass is mutable by default, supports methods and type hints, and is better for data models that change. See dataclasses for the alternative.
Modifying a ChainMap's first mapping by accident. Mutations to a ChainMap only affect the first mapping. If you intend to modify a later mapping, access it directly by index: chain.maps[1]["key"] = value.
Rune AI
Key Insights
Countercounts hashable items and provides.most_common()and arithmetic operations.defaultdictreturns a default value for missing keys, eliminatingKeyErrorchecks.dequesupports fast O(1) appends and pops from both ends.namedtuplecreates lightweight, immutable record types with named fields.OrderedDictmaintains insertion order (useful for LRU caches and ordered mappings).ChainMapgroups multiple dictionaries into a single view.
Frequently Asked Questions
When should I use defaultdict instead of a regular dict?
What is the difference between deque and list?
Should I use namedtuple or a dataclass?
Conclusion
The collections module fills gaps in Python's built-in container types. Use Counter for frequency counting, defaultdict for automatic default values, deque for fast double-ended operations, and namedtuple for lightweight record types. Each is a drop-in improvement over a hand-rolled equivalent.
More in this topic
Format Dates and Times in Python
Learn how to use strftime and strptime to format dates as strings and parse date strings into Python datetime objects.
What Is the Python Standard Library?
Learn what the Python standard library is, why it matters, and how to use its built-in modules without installing anything extra.
Encode and Decode Base64 Data in Python
Learn how to use Python's base64 module to encode binary data as text and decode Base64 strings back to bytes.