Collections

The right tool for common patterns — knowing these separates fluent Python from homework Python

Why

Picking the right container eliminates entire classes of bugs and performance issues. list.insert(0, x) is O(n); deque.appendleft(x) is O(1). x in list is O(n); x in set is O(1). Counter replaces 5 lines of frequency-counting boilerplate with one. Reaching for the right tool without being told is what separates Python fluency from just knowing the syntax.

Intuition

list — ordered, mutable, duplicates allowed. tuple — ordered, immutable, hashable (safe as dict key). set — unordered, unique, O(1) membership. dict — key→value, O(1) lookup, ordered by insertion since 3.7. The collections module extends these: Counter counts, defaultdict never raises KeyError, deque is O(1) at both ends.

Explanation
1. list, tuple, set, dict

The four core built-in containers — each with distinct trade-offs around ordering, mutability, and lookup speed. Choosing the right one prevents entire classes of bugs.

# list — ordered, mutable nums = [3, 1, 2] nums.append(4) # [3, 1, 2, 4] nums.extend([5, 6]) # add multiple nums.pop() # removes & returns last nums.insert(0, 99) # O(n) — shift everything right nums.remove(3) # removes first occurrence of value nums.sort() # in place # tuple — immutable, hashable, usable as dict key point = (3, 4) x, y = point # unpacking a, *rest = (1, 2, 3, 4) # a=1, rest=[2,3,4] # set — unordered, unique, O(1) membership s = {1, 2, 3} s.add(4) s.discard(99) # no error if missing (remove() raises KeyError) 2 in s # O(1) — use set, not list, for membership checks {1,2,3} & {2,3,4} # {2,3} — intersection {1,2,3} | {2,3,4} # {1,2,3,4} — union {1,2,3} - {2,3,4} # {1} — difference # dict — key→value, O(1) lookup, insertion-ordered (Python 3.7+) d = {"a": 1, "b": 2} d["c"] = 3 # add / update d.get("x", 0) # safe get — returns 0 if missing d.pop("a") # remove and return value "b" in d # key membership check d.keys() # dict_keys(["b", "c"]) d.values() # dict_values([2, 3]) d.items() # dict_items([("b",2), ("c",3)]) # dictionary sorting sorted(d) # keys only sorted(d.values()) # values only sorted(d.items()) # (key, value) tuples, sorted by key sorted(d.items(), key=lambda item: item[1]) # tuples, sorted by value # merge dicts (Python 3.9+) merged = d1 | d2 # new dict d1 |= d2 # in place
2. Built-in functions

Built-in iteration helpers that eliminate manual index tracking and loop boilerplate. They work with any iterable.

# enumerate — index + value, no manual counter for i, item in enumerate(["a", "b", "c"], start=1): print(i, item) # 1 a, 2 b, 3 c # zip — parallel iteration, stops at shortest for name, score in zip(names, scores): ... # any / all — short-circuit, accept generators all(x > 0 for x in values) # False on first non-positive any(x > 0 for x in values) # True on first positive
3. Counter, defaultdict, deque

The collections module extends the core containers for specific patterns: counting frequencies, safe default access, and O(1) operations at both ends of a sequence.

from collections import Counter, defaultdict, deque # Counter — frequency map, missing keys return 0 freq = Counter("abracadabra") freq.most_common(2) # [("a", 5), ("b", 2)] freq["z"] # 0 — no KeyError # defaultdict — callable fills missing keys graph = defaultdict(list) graph["A"].append("B") # no KeyError on first access # deque — O(1) at both ends q = deque(maxlen=3) # auto-evicts oldest when full q.appendleft(x) # O(1) prepend (list.insert(0,x) is O(n)) q.popleft() # O(1) pop from front
4. namedtuple / NamedTuple

A named tuple is a tuple subclass where each position has a name. Immutable, memory-efficient, and prints readably — a lightweight read-only record without the overhead of a full class.

from collections import namedtuple from typing import NamedTuple # collections.namedtuple — concise, functional style Point = namedtuple("Point", ["x", "y"]) p = Point(3, 4) p.x # 3 — attribute access p[0] # 3 — index access still works (it's a tuple) p._asdict() # {"x": 3, "y": 4} p._replace(x=10) # Point(x=10, y=4) — returns new instance # typing.NamedTuple — class-based, supports type hints and defaults class Coordinate(NamedTuple): x: float y: float z: float = 0.0 # default value c = Coordinate(1.0, 2.0) c.z # 0.0 # NamedTuple is immutable and hashable — usable as a dict key or set member # Use @dataclass when you need mutability or instance methods
5. heapq

heapq implements a min-heap on a regular list. Use it for priority queues, efficiently retrieving the k smallest/largest items, and graph algorithms like Dijkstra's.

import heapq nums = [3, 1, 4, 1, 5, 9] heapq.heapify(nums) # convert list to heap in-place, O(n) heapq.heappush(nums, 2) # push item, O(log n) heapq.heappop(nums) # pop SMALLEST item, O(log n) → 1 # nlargest / nsmallest — O(n log k), faster than full sort when k is small heapq.nlargest(3, nums) # [9, 5, 4] heapq.nsmallest(3, nums) # [1, 1, 2] # priority queue — push (priority, value) tuples tasks = [] heapq.heappush(tasks, (2, "low priority")) heapq.heappush(tasks, (0, "urgent")) heapq.heappush(tasks, (1, "medium")) heapq.heappop(tasks) # (0, "urgent") — lowest number = highest priority # max-heap trick — Python only provides min-heap, so negate values heapq.heappush(nums, -val) # push negated -heapq.heappop(nums) # negate on pop to get original value
6. Enum

Enum defines a fixed set of named constants. It replaces magic strings and numbers with self-documenting, type-safe names and makes it clear the set of values is closed.

from enum import Enum, auto class Status(Enum): PENDING = auto() # auto() assigns 1, 2, 3 ... RUNNING = auto() DONE = auto() FAILED = auto() s = Status.PENDING s.name # "PENDING" s.value # 1 # comparison — use == or is against members, never compare raw values s == Status.PENDING # True s is Status.PENDING # True # iteration — Enums are iterable for state in Status: print(state.name, state.value) # lookup by value or name Status(1) # Status.PENDING Status["RUNNING"] # Status.RUNNING # type hints def process(status: Status) -> None: ...

Use auto() when the exact value doesn't matter. If values must be specific (e.g. stored in a DB), assign them explicitly: PENDING = "pending".