Python Collections
Deep dive into list, dict, set, tuple, and the powerful collections module: deque, defaultdict, Counter, and namedtuple.
list
Lists are ordered, mutable sequences. The most-used collection in Python.
items = [1, 2, 3, "four", 5.0] # heterogeneous is fine
# Indexing and slicing
items[0] # 1
items[-1] # 5.0
items[1:3] # [2, 3]
# Mutating
items.append(6) # add to end
items.insert(2, "two.5") # insert at index 2
items.extend([7, 8]) # add multiple
items.remove("four") # remove first occurrence
popped = items.pop() # remove and return last
popped2 = items.pop(0) # remove and return by index
# Sorting
nums = [3, 1, 4, 1, 5, 9]
nums.sort() # in-place
nums.sort(reverse=True)
sorted_copy = sorted(nums) # returns new list
# Sort by key
people = [{"name": "Bob", "age": 25}, {"name": "Alice", "age": 30}]
people.sort(key=lambda p: p["age"])
# Other
nums.count(1) # 2
nums.index(5) # first index of 5
nums.reverse() # in-place reverse
nums.copy() # shallow copy
dict
Dictionaries are ordered (since Python 3.7), mutable key-value maps.
user = {"name": "Alice", "age": 30, "active": True}
# Access
user["name"] # "Alice"
user.get("email") # None (no KeyError)
user.get("email", "N/A") # "N/A" (default)
# Mutate
user["email"] = "[email protected]"
user.update({"city": "NYC", "age": 31})
# Delete
del user["active"]
email = user.pop("email", None) # remove and return
# Iterate
for key in user: # iterate keys
...
for key, value in user.items(): # iterate pairs
...
for value in user.values(): # iterate values
...
# Merge dicts (Python 3.9+)
defaults = {"timeout": 30, "retries": 3}
config = {"timeout": 60}
merged = defaults | config # {"timeout": 60, "retries": 3}
# Dict comprehension
squares = {x: x**2 for x in range(6)}
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
set
Sets are unordered collections of unique, hashable elements.
tags = {"python", "web", "api"}
tags.add("async")
tags.discard("web") # remove if present (no error)
tags.remove("api") # remove (raises KeyError if missing)
# Set operations
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
a | b # {1, 2, 3, 4, 5, 6} union
a & b # {3, 4} intersection
a - b # {1, 2} difference
a ^ b # {1, 2, 5, 6} symmetric difference
# Membership test — O(1)
"python" in tags # True
# Deduplication
unique = list(set([1, 2, 2, 3, 3, 3])) # [1, 2, 3]
tuple
Tuples are immutable sequences — use them for records and as dict keys.
point = (3.0, 4.0)
rgb = (255, 128, 0)
# Unpacking
x, y = point
r, g, b = rgb
# Extended unpacking
first, *rest = (1, 2, 3, 4, 5) # first=1, rest=[2,3,4,5]
*init, last = (1, 2, 3, 4, 5) # init=[1,2,3,4], last=5
# Tuple as dict key (lists cannot be)
cache = {}
cache[(0, 0)] = "origin"
# Single-element tuple needs trailing comma
one = (42,) # not (42) which is just int 42
collections.deque
A double-ended queue — O(1) appends and pops from both ends. Use instead of list for queues.
from collections import deque
q = deque(maxlen=3) # fixed-size circular buffer
q.append(1)
q.append(2)
q.append(3)
q.append(4) # 1 is evicted: deque([2, 3, 4])
q.appendleft(0) # prepend
q.popleft() # O(1) — unlike list.pop(0) which is O(n)
# BFS queue
from collections import deque
def bfs(graph, start):
visited = set()
queue = deque([start])
while queue:
node = queue.popleft()
if node not in visited:
visited.add(node)
queue.extend(graph[node])
return visited
collections.defaultdict
Like dict, but returns a default value for missing keys instead of raising KeyError.
from collections import defaultdict
# Group items by key
words = ["apple", "ant", "banana", "avocado", "blueberry"]
by_letter = defaultdict(list)
for word in words:
by_letter[word[0]].append(word)
# {"a": ["apple", "ant", "avocado"], "b": ["banana", "blueberry"]}
# Count occurrences
freq = defaultdict(int)
for char in "mississippi":
freq[char] += 1
# Nested defaultdict
nested = defaultdict(lambda: defaultdict(int))
nested["users"]["alice"] += 1
collections.Counter
A specialized dict for counting hashable objects.
from collections import Counter
words = ["apple", "banana", "apple", "cherry", "banana", "apple"]
count = Counter(words)
# Counter({"apple": 3, "banana": 2, "cherry": 1})
count.most_common(2) # [("apple", 3), ("banana", 2)]
count["apple"] # 3
count["grape"] # 0 (no KeyError)
# Arithmetic
c1 = Counter(a=3, b=1)
c2 = Counter(a=1, b=2)
c1 + c2 # Counter(a=4, b=3)
c1 - c2 # Counter(a=2) (negative counts removed)
# Count characters
letter_freq = Counter("hello world")
collections.namedtuple
Tuples with named fields — great for lightweight data records.
from collections import namedtuple
Point = namedtuple("Point", ["x", "y"])
p = Point(3.0, 4.0)
p.x # 3.0
p.y # 4.0
p[0] # 3.0 (indexing still works)
x, y = p # unpacking works
# Immutable — cannot change fields
# p.x = 5 raises AttributeError
# Convert to dict
p._asdict() # {"x": 3.0, "y": 4.0}
# Replace fields
p2 = p._replace(x=10.0) # Point(x=10.0, y=4.0)
For mutable records with type hints, prefer dataclasses.dataclass instead.
Choosing the Right Collection
| Need | Use |
|---|---|
| Ordered sequence, mutable | list |
| Ordered sequence, immutable | tuple |
| Key-value mapping | dict |
| Unique elements, fast membership | set |
| Fast queue (both ends) | deque |
| Counting | Counter |
| Group-by / missing key default | defaultdict |
| Lightweight record with names | namedtuple or dataclass |