Beginner18 min

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.

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:

Operationlistdict / set
x in collectionO(n) scanO(1) hash lookup
append / addO(1)O(1)
insert or delete at frontO(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.

Copy-paste examples

Copy into your own editor, or load one into the compiler below and press Run.

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)

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)

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)})

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__)

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")

Pick the right collection for each job

Try it — in-browser Python

Change the cohort lists and every set result updates automatically.

Output

Python runs in your browser. First run downloads the runtime.

Press Run (or Ctrl+Enter) to execute.

CPython in WebAssembly. Stdlib works. NumPy, pandas, scikit-learn and Matplotlib load on demand, and charts render below. No input(), no GPU, no network installs.

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.