Collections: lists, tuples, dicts and sets
Four built-in collections do most of the work in real Python programs. Learn what each is for, why looking something up in a dict or set is so much faster than in a list, and how to write collections concisely with comprehensions.
In this lecture you will
- Choose between list, tuple, dict and set for a given job
- Explain why dict and set lookups stay fast as they grow
- Explain why dict keys must be hashable
- Build collections with list, dict and set comprehensions
Nearly every real program needs to hold groups of values: rows from a file, settings, users, results. Python provides four built-in collections, each designed for a different job. Choosing the right one makes code shorter, clearer and often dramatically faster.
The four workhorses
| Example | Ordered | Changeable | Duplicates | Typical use | |
|---|---|---|---|---|---|
| list | [3, 1, 3] | yes | yes | yes | a sequence of items you add to, sort and loop over |
| tuple | ("web-1", 8080) | yes | no | yes | a fixed group of related values, such as a record or a pair |
| dict | {"asha": 101} | yes (insertion order) | yes | keys unique | looking values up by a key |
| set | {"web", "db"} | no | yes | no | unique items and fast membership tests |
servers = ["web-1", "web-2"] # list
servers.append("web-3")
endpoint = ("api.example.com", 443) # tuple
host, port = endpoint # unpacking
ext = {"asha": 101, "ravi": 102} # dict
ext["meera"] = 103
print(ext.get("john", "unknown")) # 'unknown' instead of a KeyError
tags = {"urgent", "billing"} # set
print("urgent" in tags) # True
Indexing and slicing
Lists, tuples and strings are sequences: every item has a position, counted from 0. Negative positions count from the end, so temps[-1] is the last item. A slice, written [start:stop:step], copies part of a sequence: it begins at start, stops before stop and moves step positions at a time. Leave out any part to get the default: the beginning, the end, or a step of 1.
Two facts save hours of debugging: stop is never included, and slices never raise IndexError: positions beyond the ends are simply clipped. A slice is a new list; changing it doesn’t change the original.
Why dict and set lookups are fast
Ask whether a value is in a list, and Python compares it with each item in turn. With a million items, that can mean a million comparisons. Ask the same of a dict or set, and the answer comes back almost instantly, however large it grows. The secret is a hash table.
Python computes a hash of the key — a large integer derived from its contents — and uses it to jump straight to a slot in an internal table. If that slot is taken by a different key (a collision), it tries another slot. Try inserting and looking up names below, and compare the work with a list scan.
Simplified: Python’s real hash function, table layout and probing order are more elaborate, and string hashes change between runs for security.
Because the table grows before it gets crowded, most lookups need only one or two probes. In the language of algorithms, list membership is O(n) — it grows with the number of items — while dict and set lookups are O(1) on average.
Keys must be hashable
A dict finds keys by their hash, so a key’s hash must never change while it is stored. That’s why keys (and set members) must be hashable: strings, numbers, booleans, None and tuples of these all work. Lists, dicts and sets are mutable and can’t be keys. If you need a composite key, use a tuple — routes[("DEL", "BLR")].
Comparing collections with set operations
Sets support the operations you may know from Venn diagrams, which turns “compare these two lists” into a single expression. Suppose you have the servers in your inventory and the servers that report to your monitoring system:
inventory = {"web-1", "web-2", "db-1", "cache-1", "batch-1"}
monitored = {"web-1", "web-2", "db-1", "db-2"}
Each operator has a method form, such as inventory.difference(monitored). The methods also accept lists and other iterables, so you can compare a set with a list without converting it first.
Comprehensions
A comprehension builds a new collection from an existing one in a single expression:
prices = [120, 85, 240, 60]
with_tax = [round(p * 1.18, 2) for p in prices] # list
expensive = [p for p in prices if p > 100] # filtered list
lookup = {name: len(name) for name in ["asha", "ravi"]} # dict
domains = {email.split("@")[1] for email in emails} # set of unique domains
Read it as English: “p times 1.18, for each p in prices”. Comprehensions are usually clearer than an empty collection plus a loop — until they get long. If you need nested loops and several conditions, a regular loop is kinder to the next reader.
That completes the fundamentals module. You can now read and write Python with a precise picture of what it does. Module 2 applies these foundations to real programs — text, files, errors, packages and object-oriented design.
Key takeaways
- Lists are ordered and changeable; tuples are ordered and fixed; dicts map keys to values; sets hold unique items.
- Dicts and sets use hash tables, so checking for a key takes about the same time with ten items or ten million.
- Keys and set members must be hashable — in practice, immutable values such as strings, numbers and tuples.
- Comprehensions express “build a collection from another” in one readable line.
Check your understanding
3 questions · answers explainedQ1You need to check, many times per second, whether a user ID is in a list of 2 million blocked IDs. What should you use?
Q2Why can’t a list be used as a dictionary key?
Q3What does {w.lower() for w in ["Apple", "apple", "Banana"]} produce?