List, dict, set, and tuple reference
Every method on Python's four core collections, when to use each type, and their performance characteristics.
Why this matters in AI / ML / GenAI
Choosing the wrong collection is the most common cause of slow Python. Checking membership in a list is a full scan; in a set it is instant. On a million-row dataset that is the difference between seconds and hours.
1. Read
Understand the idea in plain English first.
2. Run
Load any example into the compiler and press Run.
3. Change
Edit one value, rerun, and learn from the output.
Choosing the right collection
- list — ordered, mutable, allows duplicates. The default when you need a sequence.
- tuple — ordered, immutable. Use for fixed records and as dictionary keys, which lists cannot be.
- dict — key to value mapping, insertion-ordered since Python 3.7, keys must be hashable.
- set — unordered, unique elements, extremely fast membership tests.
Performance is the deciding factor:
| Operation | list | dict / set |
|---|---|---|
x in collection | O(n) scan | O(1) hash lookup |
| append / add | O(1) | O(1) |
| insert or delete at front | O(n) | — |
If you find yourself writing if item in big_list inside a loop, convert to a set first. Use collections.deque when you need fast appends and pops at both ends.
List and tuple methods
List: append, extend, insert, remove, pop, clear, index, count, sort, reverse, copy.
sort() sorts in place and returns None; sorted() returns a new list. Writing items = items.sort() sets items to None, and it is a classic beginner bug.
append(x) adds one element; extend(seq) adds each element of a sequence. append on a list gives you a nested list.
Tuple has only count and index — everything else would mutate. Tuples unpack cleanly (a, b = pair) and support starred unpacking (first, *rest = items).
Copying: copy() and list(x) are shallow, so nested objects are still shared. Use copy.deepcopy for nested structures.
Dict and set methods
Dict: get, keys, values, items, pop, popitem, update, setdefault, clear, copy, fromkeys.
get(key, default) never raises, which is the safe way to read config. setdefault(key, []) initialises a missing key in one step, though collections.defaultdict is cleaner when you do it repeatedly.
Merge with {**a, **b} or a | b (Python 3.9+); the right side wins on conflicts.
Set: add, remove (raises if missing), discard (silent), pop, union (|), intersection (&), difference (-), symmetric_difference (^), issubset, issuperset, isdisjoint.
Set operations are the fastest way to compare two collections: which users are in both cohorts, which features are missing from the new dataset, which tokens are out of vocabulary.
Note that {} creates an empty dict; use set() for an empty set.
Hands-on practice
Compiler on the left. Examples on the right.
On desktop, keep the compiler beside the examples. On mobile, the same blocks stack cleanly. Pick an example, try it in the compiler, then change one small thing.
Pick the right collection for each job
Live Python compiler
Try it — in-browser Python
Change the cohort lists and every set result updates automatically.
Code editor
Output
Python runs in your browser. First run downloads the runtime.
Press Run (or Ctrl+Enter) to execute.
Runs CPython in your browser. NumPy, pandas, scikit-learn and Matplotlib load on demand. Charts appear below the output. No input(), no GPU, no network installs.
Clear code examples
Every example is copy-ready. Use Try in compiler when you want to experiment without scrolling around.
Example
List methods and the sort-in-place trap
sort() returns None. sorted() returns a new list.
items = [3, 1, 4, 1, 5]
items.append(9)
items.extend([2, 6])
items.insert(0, 0)
print("after append/extend/insert:", items)
items.remove(1) # removes the FIRST 1 only
popped = items.pop() # last element
print(f"after remove(1) and pop() -> {popped}:", items)
print("index of 4:", items.index(4), "| count of 1:", items.count(1))
new_list = sorted(items, reverse=True)
print("\nsorted() returns a list:", new_list)
items.sort()
print("sort() mutates in place :", items)
print("sort() returns :", [1, 2].sort(), "<- never assign this")
print("\nappend vs extend:")
a, b = [1, 2], [1, 2]
a.append([3, 4])
b.extend([3, 4])
print(" append:", a)
print(" extend:", b)Example
Tuples, unpacking, and why immutability matters
Tuples can be dict keys; lists cannot.
point = (12.5, 48.2)
x, y = point
print(f"unpacked: x={x} y={y}")
record = ("mini", 0.91, 620, "2026-09-04")
name, score, *rest = record
print("starred unpacking:", name, score, rest)
cache = {}
cache[("mini", "en")] = 0.91
cache[("large", "fr")] = 0.88
print("\ntuple as dict key:", cache[("mini", "en")])
try:
cache[["mini", "en"]] = 0.5
except TypeError as err:
print("list as key ->", err)
print("\ntuple has only two methods:", [m for m in dir(tuple) if not m.startswith("_")])
print("swap without a temp variable:")
a, b = 1, 2
a, b = b, a
print(" a =", a, "b =", b)Example
Dict methods in practice
get for safe reads, setdefault for grouping, | to merge.
config = {"model": "gpt-4.1-mini", "temperature": 0.2}
print("get present :", config.get("model"))
print("get missing :", config.get("top_p"), "(None, no error)")
print("get default :", config.get("top_p", 1.0))
defaults = {"temperature": 0.7, "top_p": 1.0, "max_tokens": 512}
merged = defaults | config # right side wins
print("\nmerged:", merged)
grouped = {}
for model, topic in [("mini", "rag"), ("large", "agents"), ("mini", "eval")]:
grouped.setdefault(model, []).append(topic)
print("\ngrouped with setdefault:", grouped)
config.update({"stream": True, "temperature": 0.5})
print("\nafter update:", config)
print("popped:", config.pop("stream"), "| remaining keys:", list(config.keys()))
print("\niterate items:")
for key, value in merged.items():
print(f" {key:<12} {value}")
print("\ncomprehension filter:", {k: v for k, v in merged.items() if isinstance(v, float)})Example
Set operations for comparing datasets
The fastest way to diff two collections.
train_features = {"tokens", "latency", "model", "region", "user_id"}
serving_features = {"tokens", "latency", "model", "device"}
print("in both :", sorted(train_features & serving_features))
print("training only :", sorted(train_features - serving_features), "<- missing at serving time")
print("serving only :", sorted(serving_features - train_features), "<- unseen by the model")
print("either :", sorted(train_features | serving_features))
print("mismatched :", sorted(train_features ^ serving_features))
print("\nsubset? ", serving_features <= train_features)
print("disjoint?", train_features.isdisjoint({"foo", "bar"}))
s = {1, 2, 3}
s.add(4)
s.discard(99) # silent when missing
print("\nafter add/discard:", s)
try:
s.remove(99)
except KeyError:
print("remove(99) raised KeyError — use discard to be safe")
print("\ndeduplicate while keeping order:", list(dict.fromkeys([3, 1, 3, 2, 1])))
print("{} is a dict:", type({}).__name__, "| set() is a set:", type(set()).__name__)Example
Why the collection type matters for speed
Same logic, very different cost as the data grows.
import time
n = 40_000
haystack_list = list(range(n))
haystack_set = set(haystack_list)
needles = range(0, n, 1000)
start = time.perf_counter()
found_list = sum(1 for x in needles if x in haystack_list)
list_time = time.perf_counter() - start
start = time.perf_counter()
found_set = sum(1 for x in needles if x in haystack_set)
set_time = time.perf_counter() - start
print(f"list scan: {list_time * 1000:8.3f} ms ({found_list} found)")
print(f"set hash: {set_time * 1000:8.3f} ms ({found_set} found)")
print(f"set is roughly {list_time / max(set_time, 1e-9):,.0f}x faster here")
print("\nthe gap grows linearly with the size of the collection")Takeaways
- Use a set for membership tests — list scans are O(n) and dominate large loops.
- sort() mutates and returns None; sorted() returns a new list.
- dict.get and setdefault avoid KeyError; set operators diff two collections instantly.