Home Tutorials Standard Library

Standard Library

The collections Module: Specialised Containers Beyond list and dict

Pyford Notes July 1, 2026 7 min read
Key points
  • Counter(iterable) builds a frequency map in one line and supports addition, subtraction, and most_common().
  • defaultdict(factory) calls the factory to create a missing key's value instead of raising KeyError.
  • deque supports O(1) append and pop from both ends; use it for queues and sliding windows.
  • namedtuple creates a tuple subclass with named fields, making index-based access obsolete.

What collections provides

Python's built-in list, dict, set, and tuple cover most everyday programming tasks. The collections module lives in the standard library and fills the gaps with specialised alternatives that are faster, more readable, or less error-prone for specific patterns.

ClassReplaces / extendsMain use case
CounterdictFrequency counting
defaultdictdictAuto-initialise missing keys
dequelistFast double-ended queue
namedtupletupleLightweight named records
OrderedDictdictOrder-aware dict operations

Import any class directly:

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

Counter: frequency counting

Counting how often each item appears in a sequence is a very common task. Without Counter you need a loop and an if guard. Counter does it in one call:

from collections import Counter

words = ["apple", "banana", "apple", "cherry", "banana", "apple"]
freq = Counter(words)
print(freq)
# Counter({'apple': 3, 'banana': 2, 'cherry': 1})

print(freq.most_common(2))
# [('apple', 3), ('banana', 2)]

Counter objects support arithmetic. Adding two counters merges their counts; subtracting removes counts (keeping only positive results):

a = Counter({"x": 4, "y": 2})
b = Counter({"x": 1, "y": 5, "z": 3})

print(a + b)   # Counter({'y': 7, 'x': 5, 'z': 3})
print(a - b)   # Counter({'x': 3})   # only positive results

Counter also works on strings, treating each character as an element.

defaultdict: automatic missing keys

A plain dict raises KeyError when you access a key that does not exist. defaultdict accepts a factory function and calls it to supply a default value the first time a missing key is accessed:

from collections import defaultdict

groups = defaultdict(list)
data = [("fruit", "apple"), ("veggie", "carrot"), ("fruit", "pear")]

for category, item in data:
    groups[category].append(item)

print(dict(groups))
# {'fruit': ['apple', 'pear'], 'veggie': ['carrot']}

Common factories are list, set, int (defaults to 0), and str. You can also pass a lambda for any value:

dd = defaultdict(lambda: "unknown")
print(dd["missing_key"])  # "unknown"

deque: fast queue and stack

Python lists have O(1) append and pop at the right end, but O(n) insert and pop at the left end because every other element must shift. deque (double-ended queue) supports O(1) operations at both ends:

from collections import deque

q = deque([1, 2, 3])
q.appendleft(0)   # O(1)
q.append(4)       # O(1)
print(q)          # deque([0, 1, 2, 3, 4])

q.popleft()       # O(1) — removes 0
q.pop()           # O(1) — removes 4
print(q)          # deque([1, 2, 3])

Set a maxlen to create a sliding window that automatically discards the oldest entries:

recent = deque(maxlen=3)
for n in range(6):
    recent.append(n)
print(recent)  # deque([3, 4, 5], maxlen=3)

namedtuple: lightweight records

Ordinary tuples force you to remember what index 0, 1, and 2 mean. namedtuple creates a subclass of tuple with named fields. The result is immutable, memory-efficient, and self-documenting:

from collections import namedtuple

Point = namedtuple("Point", ["x", "y"])
p = Point(3, 7)

print(p.x)   # 3
print(p[0])  # 3 — index access still works
print(p)     # Point(x=3, y=7)

Named tuples are fully compatible with tuple unpacking and work as dict keys (they are hashable). For mutable records with the same named-field syntax, see dataclasses.

OrderedDict: insertion-order dict

Since Python 3.7 plain dict preserves insertion order. OrderedDict still has two advantages: its move_to_end() method and the fact that its equality comparison takes order into account (two OrderedDict objects with the same pairs in different order are not equal):

from collections import OrderedDict

od = OrderedDict([("a", 1), ("b", 2), ("c", 3)])
od.move_to_end("a")        # move "a" to the end
print(list(od.keys()))     # ['b', 'c', 'a']

od.move_to_end("c", last=False)  # move "c" to the front
print(list(od.keys()))     # ['c', 'b', 'a']

OrderedDict is useful when implementing LRU caches or any algorithm where relative ordering of keys is semantically significant.

Choosing the right container

Use this decision guide:

  • Need to count things? Use Counter.
  • Building a dict of lists, sets, or totals without boilerplate guards? Use defaultdict.
  • Need a queue or need to pop/append from both ends efficiently? Use deque.
  • Returning a group of related values from a function and want readable field names? Use namedtuple (or dataclasses if you need mutability).
  • Need order-sensitive dict equality or move_to_end? Use OrderedDict.

All five are drop-in replacements for their built-in counterparts; switching requires only an import and a constructor change.