Skip to main content
Python · lesson 10 of 34
Python beginner Lesson 10 of 34

Python Collections

Work through list, dict, set and tuple, then deque, defaultdict, Counter and namedtuple from the collections module, each with its real printed output.

By SkillByExample · Updated

Python ships four built-in collections — list, dict, set, tuple — and the collections module adds specialised ones for queues, counting and grouping. All output below is from Python 3.13.

list

Lists are ordered and mutable; most methods change the list in place and return None.

items = [3, 1, 4, 1, 5]
items.append(9)
items.insert(0, 2)
items.remove(1)              # first 1 only
print(items)
print(items.pop(), items)

result = items.sort()
print(result, items)

print(sorted(items, reverse=True), items)
print(items[1:3], items[-1], items.count(1))
$ python lists.py
[2, 3, 4, 1, 5, 9]
9 [2, 3, 4, 1, 5]
None [1, 2, 3, 4, 5]
[5, 4, 3, 2, 1] [1, 2, 3, 4, 5]
[2, 3] 5 1

items.sort() returned None — it sorts in place, so result = items.sort() is a common bug. sorted() builds a new list and leaves the original alone. remove(1) deleted only the first 1, which is why count(1) is still 1.

dict

Dicts map keys to values and keep insertion order (guaranteed since 3.7).

user = {"name": "Alice", "age": 30}

print(user.get("email"), user.get("email", "N/A"))
try:
    user["email"]
except KeyError as e:
    print("KeyError:", e)

user["email"] = "alice@example.com"
user.update(age=31)
print(user)

defaults = {"timeout": 30, "retries": 3}
merged = defaults | {"timeout": 60}     # right side wins
print(merged)

for key, value in merged.items():
    print(f"{key}={value}")
print({n: n * n for n in range(4)})
$ python dicts.py
None N/A
KeyError: 'email'
{'name': 'Alice', 'age': 31, 'email': 'alice@example.com'}
{'timeout': 60, 'retries': 3}
timeout=60
retries=3
{0: 0, 1: 1, 2: 4, 3: 9}

user["email"] raises for a missing key; .get() returns None or your default. Updating age kept it in its original position — only new keys go to the end. In defaults | {...} the right-hand dict wins on conflicts.

set

Sets hold unique hashable values and test membership in O(1).

a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
print(a | b, a & b, a - b, a ^ b)

print(sorted(set([3, 1, 3, 2, 1])))

tags = {"python", "web"}
tags.add("api")
tags.discard("missing")      # silently does nothing
print("api" in tags, sorted(tags))

try:
    {[1, 2]}
except TypeError as e:
    print("TypeError:", e)
$ python sets.py
{1, 2, 3, 4, 5, 6} {3, 4} {1, 2} {1, 2, 5, 6}
[1, 2, 3]
True ['api', 'python', 'web']
TypeError: unhashable type: 'list'

The four operators are union, intersection, difference and symmetric difference. The string set is printed through sorted() deliberately: string hashes are randomised per process, so print(tags) could show a different order every run. A list can’t go in a set because it is mutable, and so unhashable.

tuple

Tuples are immutable sequences — handy for records, unpacking, and dict keys.

point = (3.0, 4.0)
x, y = point
first, *rest = (1, 2, 3, 4, 5)
print(x, y, first, rest)

cache = {(0, 0): "origin"}
print(cache[(0, 0)])

print(type((42)), type((42,)))

try:
    point[0] = 9.0
except TypeError as e:
    print("TypeError:", e)
$ python tuples.py
3.0 4.0 1 [2, 3, 4, 5]
origin
<class 'int'> <class 'tuple'>
TypeError: 'tuple' object does not support item assignment

Starred unpacking collects the leftovers into a list, not a tuple. (42) is just the integer 42 in parentheses — the comma, not the parentheses, makes a tuple.

collections.deque

A double-ended queue with O(1) operations at both ends; maxlen makes it a ring buffer.

from collections import deque

recent = deque(maxlen=3)
for n in range(1, 6):
    recent.append(n)
print(recent)

recent.appendleft(0)
print(recent)
print(recent.popleft(), recent)

graph = {"A": ["B", "C"], "B": ["D"], "C": ["D"], "D": []}
queue, order = deque(["A"]), []
while queue:
    node = queue.popleft()
    if node not in order:
        order.append(node)
        queue.extend(graph[node])
print("BFS:", order)
$ python deques.py
deque([3, 4, 5], maxlen=3)
deque([0, 3, 4], maxlen=3)
0 deque([3, 4], maxlen=3)
BFS: ['A', 'B', 'C', 'D']

Appending to a full deque evicts from the opposite end: append dropped 1 and 2, and appendleft(0) pushed out 5. popleft() is what makes the breadth-first search cheap — on a list, pop(0) would shift every element.

collections.defaultdict

A dict that calls a factory to create the value for a missing key.

from collections import defaultdict

words = ["apple", "ant", "banana", "avocado", "blueberry"]
by_letter = defaultdict(list)
for word in words:
    by_letter[word[0]].append(word)
print(dict(by_letter))

freq = defaultdict(int)
for ch in "mississippi":
    freq[ch] += 1
print(dict(freq))

print(by_letter["z"], "z" in by_letter)
$ python defaultdicts.py
{'a': ['apple', 'ant', 'avocado'], 'b': ['banana', 'blueberry']}
{'m': 1, 'i': 4, 's': 4, 'p': 2}
[] True

list and int are the factories: a missing key gets [] or 0, so append and += 1 work on first sight. The catch is the last line — merely reading by_letter["z"] inserted the key. Use .get() or in when you only want to look.

collections.Counter

A dict subclass for counting hashable things.

from collections import Counter

count = Counter(["apple", "banana", "apple", "cherry", "banana", "apple"])
print(count)
print(count.most_common(2))
print(count["grape"])

c1 = Counter(a=3, b=1)
c2 = Counter(a=1, b=2)
print(c1 + c2, c1 - c2)
print(Counter("hello world").most_common(3))
$ python counters.py
Counter({'apple': 3, 'banana': 2, 'cherry': 1})
[('apple', 3), ('banana', 2)]
0
Counter({'a': 4, 'b': 3}) Counter({'a': 2})
[('l', 3), ('o', 2), ('h', 1)]

A missing key reads as 0 without being inserted, unlike defaultdict. Subtraction drops counts that fall to zero or below, so b vanished from c1 - c2. Ties in most_common keep first-seen order: h beats e because it appeared first.

collections.namedtuple

A tuple whose positions also have names.

from collections import namedtuple

Point = namedtuple("Point", ["x", "y"])
p = Point(3.0, 4.0)
print(p, p.x, p[1])

x, y = p
print(p._asdict())
print(p._replace(x=10.0), p)

try:
    p.x = 5
except AttributeError as e:
    print("AttributeError:", e)
$ python namedtuples.py
Point(x=3.0, y=4.0) 3.0 4.0
{'x': 3.0, 'y': 4.0}
Point(x=10.0, y=4.0) Point(x=3.0, y=4.0)
AttributeError: can't set attribute

_replace returns a new tuple; the original p is unchanged because namedtuples are immutable. For mutable records with type hints, use a dataclass instead.

Choosing the right collection

NeedUse
Ordered sequence, mutablelist
Ordered sequence, immutable / hashable recordtuple
Key-value mappingdict
Unique elements, fast membershipset
Fast queue (both ends), ring bufferdeque
CountingCounter
Group-by / missing-key defaultdefaultdict
Lightweight named recordnamedtuple or dataclass

Practice

1. Given [5, 3, 5, 1, 3, 3], produce the unique values sorted in descending order using a set.

Solution
nums = [5, 3, 5, 1, 3, 3]
print(sorted(set(nums), reverse=True))
$ python unique_desc.py
[5, 3, 1]

set() removes duplicates; sorted(..., reverse=True) turns the unordered set back into an ordered list.

2. Sort the list of dicts [{"name": "Bob", "age": 25}, {"name": "Alice", "age": 30}, {"name": "Cy", "age": 25}] by age, then by name.

Solution
people = [{"name": "Bob", "age": 25}, {"name": "Alice", "age": 30}, {"name": "Cy", "age": 25}]
people.sort(key=lambda p: (p["age"], p["name"]))
for p in people:
    print(p["age"], p["name"])
$ python sort_people.py
25 Bob
25 Cy
30 Alice

A tuple key sorts by its first element, then breaks ties with the next one.

3. Use Counter to find the two most common words in "the cat and the hat and the bat".

Solution
from collections import Counter

text = "the cat and the hat and the bat"
print(Counter(text.split()).most_common(2))
$ python top_words.py
[('the', 3), ('and', 2)]

4. Use a deque(maxlen=3) to print the moving average of the last three readings for [10, 20, 30, 40, 50].

Solution
from collections import deque

window = deque(maxlen=3)
for reading in [10, 20, 30, 40, 50]:
    window.append(reading)
    print(reading, sum(window) / len(window))
$ python moving_avg.py
10 10.0
20 15.0
30 20.0
40 30.0
50 40.0

Until the deque is full the average covers fewer values; after that the oldest reading falls off automatically.

5. Group ["apple", "bob", "cat", "dove", "egg"] by word length with a defaultdict, then show that checking 5 in groups does not create a key.

Solution
from collections import defaultdict

groups = defaultdict(list)
for w in ["apple", "bob", "cat", "dove", "egg"]:
    groups[len(w)].append(w)
print(dict(groups))
print(5 in groups, 7 in groups, sorted(groups))
$ python by_length.py
{5: ['apple'], 3: ['bob', 'cat', 'egg'], 4: ['dove']}
True False [3, 4, 5]

in never calls the factory — only groups[key] on a missing key does.

Frequently Asked Questions

When should I use a tuple instead of a list?
Use a tuple for a fixed-size record whose positions mean different things, such as a coordinate, and a list for a variable-length sequence of similar items. Tuples of hashable values are hashable, so they can be dict keys or set members; lists cannot.
What's the time complexity of dict lookups?
O(1) on average. A Python dict is a hash table, so lookups, insertions and deletions take roughly constant time regardless of size; only pathological hash collisions degrade that.
What makes a set element or dict key valid?
It must be hashable: its hash must never change while it is stored. Immutable built-ins such as int, str and a tuple of hashables qualify; list, dict and set do not and raise TypeError: unhashable type.
Why does printing a set of strings give a different order each run?
Sets are unordered, and string hashes are randomized per process for security, so the iteration order of a set of strings can change between runs. Sort the set before printing it if you need stable output.
Why use deque instead of a list for a queue?
list.pop(0) shifts every remaining element, which is O(n). collections.deque pops and appends at both ends in O(1), and maxlen turns it into a fixed-size buffer that discards the oldest item automatically.