Warming up the neural circuits...
By the end of this chapter you will:
Ordered collections are the backbone of real programs. Every app processes lists of data — users, products, messages, prices. Without lists, you'd create a separate variable for each item. With lists, you store, access, and transform collections of data with a single name.
Python has two main sequence types: lists (mutable) and tuples (immutable). Lists are your daily workhorse — you'll use them everywhere. Tuples are for data that shouldn't change — coordinates, database rows, function returns. Master both, and you can model any ordered collection.
Lists are ordered, mutable sequences. Click each category to see the methods you'll use daily:
# Creating lists
empty = []
numbers = [1, 2, 3, 4, 5]
mixed = [1, "hello", 3.14, True, None]
from_range = list(range(5)) # [0, 1, 2, 3, 4]
# Accessing elements
fruits = ["apple", "banana", "cherry", "date"]
print(fruits[0]) # 'apple' (first)
print(fruits[-1]) # 'date' (last)
print(fruits[1:3]) # ['banana', 'cherry'] (slice)| Operation | Code | Result |
|---|---|---|
| Create empty | [] | Empty list |
| Create with values | [1, 2, 3] | List of 3 items |
| First | items[0] | First item |
| Last element | items[-1] | Last item |
| Slice | items[1:3] | Items 1-2 |
| Length | len(items) | Number of items |
fruits = ["apple", "banana"]
# Adding
fruits.append("cherry") # ['apple', 'banana', 'cherry']
fruits.insert(1, "avocado") # ['apple', 'avocado', 'banana', 'cherry']
fruits.
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# Basic slicing
print(numbers[2:5]) # [2, 3, 4]
print(numbers
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
# sort() — mutates original
numbers.sort()
print(numbers) # [1, 1, 2, 3, 4, 5, 6, 9]
# sorted() — returns new list
original =
fruits = ["apple", "banana", "cherry", "banana"]
# Membership
print("banana" in fruits) # True
print("grape" in fruits
Think of lists as ordered containers:
Lists are mutable — you can change them after creation. This is powerful but causes bugs when two variables point to the same list.
# The aliasing bug
a = [1, 2, 3]
b = a # b points to the SAME list
b.append(4)
print(a) # [1, 2, 3, 4] — a changed too!Why? b = a doesn't copy the list. Both a and b point to the same object in memory. Changing b changes a because they're the same list.
Fix: make a copy
a = [1, 2, 3]
b = a[:] # shallow copy
b.append(4)
print(a) # [1, 2, 3] — a unchangedWhen to copy:
Tuples are like lists but immutable — you can't change them after creation.
# Creating tuples
point = (3, 4)
single = (42,) # trailing comma for single-element tuple
empty = ()
from_list = tuple([1, 2, 3])
# Accessing (same as lists)
print(point[
Key differences from lists:
| Feature | List | Tuple |
|---|---|---|
| Mutable | ✅ Yes | ❌ No |
| Syntax | [1, 2, 3] | (1, 2, 3) |
| Methods | Many | Few (count, index) |
| Use when | Data changes | Data is fixed |
| Hashable | ❌ No | ✅ Yes (if contents are) |
When to use tuples:
point = (3, 4)user = (1, "Alice", 30)return name, age{(0, 0): "origin"}Tuple unpacking:
point = (3, 4)
x, y = point
print(x) # 3
print(y) # 4
# Swap
a, b = 1, 2
a, b =Processing CSV data:
# Parse CSV rows into lists
rows = [
["Alice", 30, "Engineer"],
["Bob", 25, "Designer"],
["Charlie", 35, "Manager"],
]
# Extract names
Building a shopping cart:
cart = []
def add_item(name, price, quantity):
cart.append({"name": name, "price": price, "quantity": quantity})
def total():
return sum(
| Mistake | Why it's wrong | Fix |
|---|---|---|
b = a then modify b | Both point to same list — a changes too | b = a[:] or b = a.copy() |
a.sort() when you need original | sort() mutates in place | Use sorted(a) for new list |
a[10] on short list | IndexError: list index out of range | Check len(a) first or use try/except |
a.index("x") when not in list | ValueError | Use "x" in a first |
not for single tuple |
Performance: List operations are O(1) for append/pop from end, O(n) for insert/remove at arbitrary positions. For large datasets with frequent insertions, consider collections.deque.
Memory: Lists store references to objects, not copies. A list of 1 million integers stores 1 million references (8 bytes each on 64-bit Python), not the integers themselves.
Thread safety: List operations are not atomic. In multi-threaded code, use threading.Lock or switch to queue.Queue.
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], extract: first 3, last 3, every 2nd, reversed.users = [{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}, {"name": "Charlie", "age": 35}]a = [1, 2, 3]; b = a; b.append(4); print(a)Beginner:
"What's the difference between a list and a tuple?"
Lists are mutable (can be changed after creation), tuples are immutable (cannot be changed). Lists use
[], tuples use(). Tuples can be used as dictionary keys (if contents are hashable), lists cannot.
"What does list.sort() return?"
sort()returnsNone. It mutates the list in place. Usesorted(list)if you need a new sorted list without modifying the original.
Senior:
"Why is b = a[:] safer than b = a for lists?"
b = acreates an alias — both variables point to the same list object. Modifyingmodifies . creates a shallow copy — a new list with the same elements. Modifying doesn't affect . However, if the list contains mutable objects (like nested lists), the inner objects are still shared.
[] to create, append() to add, pop() to remove.list[start:stop:step]. [::-1] reverses, [:] copies.sort() mutates in place, sorted() returns new list. Use key= for custom sort.b = a doesn't copy. Use b = a[:] or b = a.copy().I have a list. I want to…
├── Add to end → append(x)
├── Add at position → insert(i, x)
├── Remove by value → remove(x)
├── Remove by index → pop(i) or del list[i]
├── Sort → sort() or sorted()
├── Find → index(x) or in
├── Copy → list[:] or list.copy()
├── Reverse → reverse() or [::-1]
└── Tuple → (1, 2, 3) — immutableWhat does list.sort() return?
| Method | Returns | Mutates | Use when |
|---|---|---|---|
append(x) | None | ✅ Yes | Add one item to end |
insert(i, x) | None | ✅ Yes | Add at specific position |
extend(iterable) | None | ✅ Yes | Add multiple items |
remove(x) | None | ✅ Yes | Delete first occurrence of value |
pop(i) | The item | ✅ Yes | Delete and return by |
del items[i] | None | ✅ Yes | Delete by index (no return) |
| Slice | Meaning |
|---|---|
[2:5] | Index 2 to 4 |
[:3] | Start to index 2 |
[7:] | Index 7 to end |
[::2] | Every 2nd item |
[::-1] | Reversed |
[:] | Shallow copy |
| Method | Returns | Mutates | Use when |
|---|---|---|---|
sort() | None | ✅ Yes | Sort in place |
sorted(list) | New list | ❌ No | Keep original |
reverse=True | — | — | Descending order |
key=fn | — | — | Custom sort order |
| Operation | Code | Result |
|---|---|---|
| Check exists | "x" in list | True/False |
| Find index | list.index("x") | Index or ValueError |
| Count | list.count("x") | Number of occurrences |
Warning: index() raises ValueError if not found. Use in first or try/except.
(42,)(42) is just 42 (int) |
Add trailing comma: (42,) |
a = [[0]*3]*3 for 2D list | All rows are same object — modify one, modify all | a = [[0]*3 for _ in range(3)] |
ab = a[:]ba"What's the time complexity of list.append() vs list.insert(0, x)?"
append()is O(1) amortized — it adds to the end.insert(0, x)is O(n) — it shifts all existing elements one position right. For frequent insertions at the front, usecollections.dequewhich is O(1) for both ends.