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.

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:

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.

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.