Lists & tuples.
Store many things.
One variable holds one value. But real programs handle collections — a list of users, a set of coordinates, a history of moves. Python gives you two tools for this: lists (changeable) and tuples (fixed). Learn both, and every program you write from here gets easier.
A Python list is an ordered, mutable collection written as [1, 2, 3]. A tuple is an ordered, immutable collection written as (1, 2, 3). Use lists when you need to add, remove, or change items. Use tuples when the data is fixed — coordinates, RGB colors, dictionary keys, or the return value of a function. Both support indexing (x[0]) and slicing (x[1:3]).
Before this lesson: you should be comfortable with variables, print(), and running a .py file. If not, start with Lesson 1.
📋 Lists — the mutable collection
Create, read, and modify. The four operations you'll use every day.
A list is a container that holds multiple values in order. Square brackets, comma-separated. It can hold any type — numbers, strings, other lists, even mixed types.
# Create a list
fruits = ["apple", "banana", "cherry"]
numbers = [10, 20, 30, 40]
mixed = [1, "hello", 3.14, True]
empty = []
# Read by index (zero-based)
print(fruits[0]) # apple
print(fruits[2]) # cherry
print(fruits[-1]) # cherry (last item)
# Modify by index
fruits[1] = "blueberry"
print(fruits) # ['apple', 'blueberry', 'cherry']
# Length
print(len(fruits)) # 3
# Check membership
print("apple" in fruits) # True
print("grape" in fruits) # False
Zero-based indexing
First item is list[0]. Last is list[-1]. list[len(list)] raises IndexError — there's no item there.
Ordered
Items stay in the order you put them. [1, 2] is not equal to [2, 1]. This matters for iteration and comparison.
Mutable
You can change any item: fruits[1] = "x". You can add or remove items. The list stays the same object in memory.
Any type allowed
Lists don't enforce a type. [1, "a", True] is valid. In practice, keep lists homogeneous — mixed types are usually a sign you want a dict.
🔧 List methods — add, remove, sort
The eight methods that cover 95% of what you'll do with lists.
nums = [3, 1, 4, 1, 5]
# ADD
nums.append(9) # add one item to the end
nums.extend([2, 6]) # add multiple items to the end
nums.insert(0, 7) # insert at index 0
# REMOVE
nums.remove(1) # remove first occurrence of value 1
last = nums.pop() # remove and return last item
first = nums.pop(0) # remove and return item at index 0
del nums[0] # remove by index (no return value)
nums.clear() # empty the list
# ORDER
words = ["banana", "apple", "cherry"]
words.sort() # sort in place: ['apple', 'banana', 'cherry']
words.sort(reverse=True) # reverse alphabetical
words.reverse() # reverse current order (not sorted)
# SEARCH
print(words.index("apple")) # index of first "apple"
print(words.count("apple")) # how many times "apple" appears
# COPY
copy1 = words.copy() # shallow copy
copy2 = words[:] # also shallow copy
In-place vs return. Methods like .append(), .sort(), .reverse(), .remove() modify the list in place and return None. Don't write nums = nums.append(5) — that sets nums to None. Either call the method on its own line, or use functions like sorted(nums) that return a new list.
The = trap. b = a does not copy a list — it makes b another name for the same list. Change a, and b changes too. For a real copy use b = a.copy() or b = a[:]. For nested lists, use import copy; b = copy.deepcopy(a). This is the #1 Python gotcha.
✂️ Slicing — extract a sub-list
The syntax list[start:stop:step] returns a new list. Never modifies the original.
letters = ['a', 'b', 'c', 'd', 'e', 'f']
# 0 1 2 3 4 5
letters[1:4] # ['b', 'c', 'd'] (start inclusive, stop exclusive)
letters[:3] # ['a', 'b', 'c'] (from start)
letters[3:] # ['d', 'e', 'f'] (to end)
letters[:] # ['a', ..., 'f'] (full copy)
letters[::2] # ['a', 'c', 'e'] (every 2nd)
letters[::-1] # ['f', 'e', 'd', 'c', 'b', 'a'] (reversed)
letters[-3:] # ['d', 'e', 'f'] (last 3)
letters[:-2] # ['a', 'b', 'c', 'd'] (all but last 2)
start is inclusive
letters[1:4] starts at index 1 ('b') — includes the item at start.
stop is exclusive
letters[1:4] stops before index 4 — the last item is 'd', not 'e'. This trips everyone up at first.
Omitted defaults
Omit start → starts at 0. Omit stop → goes to the end. Omit both → copies the whole list.
Negative step reverses
list[::-1] is the idiomatic way to reverse a list. list[::-2] takes every second item from the end.
🔒 Tuples — the immutable collection
Same as lists, but you can't change them after creation. That's the whole point.
A tuple is a list that can't change. Parentheses instead of square brackets. You can read items, slice it, iterate it, and check membership — but you cannot add, remove, or replace items. Why would you want that? Because immutability is a feature: it guarantees the data won't accidentally change, and it's faster and uses less memory than a list.
# Create a tuple
point = (3, 7)
rgb = (255, 128, 0)
single = (42,) # note the trailing comma!
# Read
print(point[0]) # 3
print(point[-1]) # 7
x, y = point # unpacking: x=3, y=7
print(x, y) # 3 7
# This fails — tuples are immutable
# point[0] = 10 → TypeError
# Tuples as dictionary keys (lists can't do this)
distances = {
(0, 0): "origin",
(1, 1): "diagonal",
}
print(distances[(0, 0)]) # origin
# Returning multiple values from a function
def min_max(nums):
return min(nums), max(nums) # returns a tuple
lo, hi = min_max([3, 1, 4, 1, 5])
print(lo, hi) # 1 5
Single-item tuple needs a trailing comma. (42) is just the number 42 in parentheses. (42,) is a tuple. This bites everyone once. If you're getting a weird TypeError: 'int' object is not iterable, check for missing commas.
⚖️ List vs tuple — which should you use?
The one decision that matters when you're storing multiple values.
| Property | List | Tuple |
|---|---|---|
| Syntax | [1, 2, 3] | (1, 2, 3) |
| Mutable? | Yes | No |
| Can be dict key? | No | Yes |
| Memory | Larger | Smaller |
| Speed | Slower | Faster |
| Use when | Data changes | Data is fixed |
Use a list for…
A to-do list. A shopping cart. Search results. A game's high scores. Anything that grows or shrinks while the program runs.
Use a tuple for…
Coordinates (x, y). RGB colors. Dates (year, month, day). Return values from a function. Dictionary keys.
The rule of thumb: if you'd say "the items" (plural, changeable), use a list. If you'd say "the pair" or "the set" (fixed shape, never changes), use a tuple. (x, y) is a point — you never add a third coordinate. [1, 2, 3] is a collection — you might.
💻 A complete example
Real code that uses lists, tuples, slicing, and methods together.
# score_tracker.py — a mini leaderboard
# List of (name, score) tuples
scores = [
("Alice", 92),
("Bob", 85),
("Charlie", 98),
("Diana", 79),
]
# Add a new player
scores.append(("Eve", 88))
# Sort by score descending — key function tells sort() what to compare
scores.sort(key=lambda pair: pair[1], reverse=True)
# Print the top 3
print("Top 3:")
for name, score in scores[:3]: # slice + unpack
print(f" {name}: {score}")
# Find the average score
total = sum(score for name, score in scores)
average = total / len(scores)
print(f"\nAverage: {average:.1f}")
# Output:
# Top 3:
# Charlie: 98
# Alice: 92
# Eve: 88
#
# Average: 88.4
Why tuples inside the list? Each player is a fixed pair — a name and a score that belong together. Making each pair a tuple signals "this is one thing with two parts." If you later add a third field (level, timestamp), switch that pair to a small dict or a class. Until then, a tuple is the right size.
🏆 Challenge
Two short tasks. Do both before moving on.
Task 1 — Shopping cart
Create a list called cart with three items: "bread", "milk", "eggs". Then:
1. Add "butter" to the end.
2. Remove "milk".
3. Print the total number of items.
4. Print the last item using negative indexing.
Expected output: 3 items, last item is "butter".
Task 2 — Coordinates
Create a tuple called start with (0, 0) and a tuple called end with (3, 4). Then:
1. Unpack start into x1, y1.
2. Unpack end into x2, y2.
3. Calculate the distance between them: ((x2-x1)2 + (y2-y1) 2) ** 0.5.
4. Print the result with an f-string.
Expected output: Distance: 5.0 (it's a 3-4-5 triangle).
⚠️ Common mistakes
The list and tuple bugs that trip up every beginner.
b = a makes b another name for a. Both point to the same list. Fix: b = a.copy() or b = a[:]. This bug is silent — no error, just wrong data spreading through your program.
.append() returns None. So nums = nums.append(5) sets nums to None. Fix: just write nums.append(5) on its own line.
list[1:4] returns indices 1, 2, 3 — not 4. The stop is exclusive. If you want the item at index 4 included, write list[1:5].
(42) is an int. (42,) is a tuple. Missing the comma causes "not iterable" errors and unpacking failures that are hard to spot.
for item in mylist: mylist.remove(item) skips items. Iterate over a copy: for item in mylist[:]:. Or better, build a new list with a comprehension.
point[0] = 10 raises TypeError. Tuples are immutable. If you need to change it, rebuild it: point = (10, point[1]).
You accessed list[3] on a 3-item list — the valid indices are 0, 1, 2. Check len(list) before indexing dynamically, or use negative indices from the end.
list.sort() sorts in place and returns None. sorted(list) returns a new sorted list. x = list.sort() sets x to None — one of the most common beginner bugs.
➡️ What's next
Lists and tuples unlock the next three lessons.
❓ Frequently asked questions
What is the difference between a list and a tuple in Python?
Lists are mutable — you can add, remove, or change items after creation. Tuples are immutable — once created, they cannot change. Use lists for collections that grow or shrink. Use tuples for fixed data, dictionary keys, and returning multiple values from a function. Lists use square brackets []; tuples use parentheses ().
How do I add an item to a Python list?
Use .append(item) to add one item to the end, .extend(iterable) to add multiple items, or .insert(index, item) to add at a specific position. For example: mylist.append(5) adds 5 to the end. These methods modify the list in place and return None — they do not return the new list.
What does Python list indexing start at?
Zero. The first item is at index 0, the second at index 1. Negative indexing counts from the end: -1 is the last item, -2 is the second to last. Accessing an index that doesn't exist raises an IndexError, so check the length with len() first if you're unsure.
How does Python list slicing work?
Slicing uses the syntax list[start:stop:step]. start is inclusive, stop is exclusive, step defaults to 1. mylist[1:4] returns items at indices 1, 2, 3 — not 4. mylist[::-1] reverses the list. Omit start (default 0) or stop (default end) to go to the boundary. Slicing always returns a new list, never modifies the original.
How do I remove items from a Python list?
Use .remove(value) to remove the first occurrence of a value (raises ValueError if not found), .pop(index) to remove and return the item at an index (default: last item), .clear() to empty the list, or the del statement (del mylist[0]) to remove by index. Choose .remove() when you know the value, .pop() when you know the position or need the removed item.
Why does my Python list change when I copy it?
Because = creates a reference, not a copy. list_a = list_b makes both variables point to the same list — changes to one affect the other. For a shallow copy use list_b = list_a.copy() or list_b = list_a[:]. For a deep copy of nested lists, use import copy; list_b = copy.deepcopy(list_a). This is the single most common Python gotcha.
Can a Python list hold mixed types?
Yes. Python lists can hold any mix of types: [1, "hello", 3.14, True, [1, 2]]. This is different from arrays in C or Java. In practice, mixing types in one list is usually a sign the data should be a dictionary or a class instead — but the language doesn't stop you.
What is a list comprehension in Python?
A list comprehension is a one-line way to build a list from an iterable. Syntax: [expression for item in iterable if condition]. For example: [x*2 for x in range(5) if x % 2 == 0] returns [0, 4, 8]. It's faster than a for loop with .append() and more readable once you're used to the pattern.