Learn how to store, access, and manipulate ordered collections of data using Python's two core sequence types: lists (mutable) and tuples (immutable).
- Creating lists (literal syntax,
list()constructor) - Indexing and negative indexing
- Slicing (same rules as strings)
- Modifying lists:
append(),insert(),extend(),remove(),pop(),clear() - Sorting:
sort()vssorted(),reverseparameter,keyparameter - Other useful operations:
len(),in,index(),count(),min(),max(),sum() - Nested lists (lists of lists, accessing nested elements)
- Copying lists: shallow copy gotchas
- Tuples — the immutable cousin
- When to use tuples vs lists
- Named tuples (brief intro)
- Variables and types
- Basic string operations (slicing will feel familiar)
A list is an ordered, mutable collection. You can put anything in a list — numbers, strings, other lists, a mix of everything. Use square brackets:
fruits = ["apple", "banana", "cherry"]
numbers = [10, 20, 30, 40, 50]
mixed = [1, "hello", 3.14, True, None]
empty = []You can also use the list() constructor to convert other iterables into lists:
letters = list("hello") # ['h', 'e', 'l', 'l', 'o']
numbers = list(range(5)) # [0, 1, 2, 3, 4]
another_copy = list([1, 2, 3]) # [1, 2, 3] — works but pointlessEvery item in a list has a position (index), starting at 0:
fruits = ["apple", "banana", "cherry", "date"]
# 0 1 2 3
print(fruits[0]) # "apple" — first item
print(fruits[2]) # "cherry" — third itemNegative indexes count backwards from the end:
print(fruits[-1]) # "date" — last item
print(fruits[-2]) # "cherry" — second to lastThis is super handy when you need the last element and don't know (or care) how long the list is.
Slicing extracts a portion of the list. The syntax is list[start:stop:step] — the same rules you learned with strings:
nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
nums[2:5] # [2, 3, 4] — index 2 up to (not including) 5
nums[:3] # [0, 1, 2] — from the beginning
nums[7:] # [7, 8, 9] — to the end
nums[::2] # [0, 2, 4, 6, 8] — every other item
nums[::-1] # [9, 8, 7, ...] — reversed copySlicing always returns a new list. The original stays untouched.
Lists are mutable — you can change them after creation. This is one of the biggest differences from strings and tuples.
fruits = ["apple", "banana"]
fruits.append("cherry") # Add to the end: ["apple", "banana", "cherry"]
fruits.insert(1, "blueberry") # Insert at index 1: ["apple", "blueberry", "banana", "cherry"]
fruits.extend(["date", "elderberry"]) # Add multiple items to the endCommon mistake: append() adds one item. If you append a list, you get a nested list. Use extend() to merge lists.
a = [1, 2]
a.append([3, 4]) # [1, 2, [3, 4]] — probably not what you wanted
b = [1, 2]
b.extend([3, 4]) # [1, 2, 3, 4] — this is probably what you wantedfruits = ["apple", "banana", "cherry", "banana"]
fruits.remove("banana") # Removes the FIRST occurrence: ["apple", "cherry", "banana"]
popped = fruits.pop() # Removes and returns the LAST item: "banana"
popped = fruits.pop(0) # Removes and returns item at index 0: "apple"
fruits.clear() # Removes everything: []You can also reassign by index or delete by index:
colors = ["red", "green", "blue"]
colors[1] = "yellow" # ["red", "yellow", "blue"]
del colors[0] # ["yellow", "blue"]There are two ways to sort, and the difference matters:
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
numbers.sort()
print(numbers) # [1, 1, 2, 3, 4, 5, 6, 9]numbers = [3, 1, 4, 1, 5, 9, 2, 6]
ordered = sorted(numbers)
print(ordered) # [1, 1, 2, 3, 4, 5, 6, 9]
print(numbers) # [3, 1, 4, 1, 5, 9, 2, 6] — still the original orderBoth sort() and sorted() accept reverse and key parameters:
# Descending order
numbers.sort(reverse=True) # [9, 6, 5, 4, 3, 2, 1, 1]
# Sort by a custom rule — key is a function applied to each element
words = ["banana", "pie", "strawberry", "fig"]
words.sort(key=len) # ["pie", "fig", "banana", "strawberry"]
# Sort case-insensitively
names = ["alice", "Bob", "CHARLIE"]
sorted_names = sorted(names, key=str.lower) # ["alice", "Bob", "CHARLIE"]A classic gotcha: sort() returns None, not the sorted list. Don't do x = my_list.sort() — x will be None.
nums = [3, 1, 4, 1, 5, 9]
len(nums) # 6 — number of items
5 in nums # True — membership check
99 in nums # False
nums.index(4) # 2 — index of first occurrence (raises ValueError if not found)
nums.count(1) # 2 — how many times 1 appears
min(nums) # 1
max(nums) # 9
sum(nums) # 23Lists can contain other lists. This is how you represent grids, tables, or matrices:
matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
]
print(matrix[0]) # [1, 2, 3] — first row
print(matrix[0][1]) # 2 — first row, second column
print(matrix[2][2]) # 9 — third row, third columnYou can go as deep as you need, but if you find yourself nesting more than two or three levels, it's usually a sign you should use a class or a different data structure.
This is where beginners get bitten. Assigning a list to a new variable does not copy it — both variables point to the same list:
a = [1, 2, 3]
b = a # b is NOT a copy — it's the same list
b.append(4)
print(a) # [1, 2, 3, 4] — surprise! a changed tooTo make a shallow copy (a new list with the same elements):
a = [1, 2, 3]
b = a[:] # Slice copy
c = a.copy() # .copy() method
d = list(a) # list() constructorNow b, c, and d are independent copies. Changing one won't affect the others.
But watch out with nested lists. A shallow copy only copies the outer list. The inner lists are still shared:
original = [[1, 2], [3, 4]]
shallow = original.copy()
shallow[0][0] = 99
print(original) # [[99, 2], [3, 4]] — the inner list changed!For a truly independent copy of nested structures, use copy.deepcopy():
import copy
original = [[1, 2], [3, 4]]
deep = copy.deepcopy(original)
deep[0][0] = 99
print(original) # [[1, 2], [3, 4]] — safe!A tuple is like a list, but you cannot change it after creation. Use parentheses instead of square brackets:
point = (3, 4)
rgb = (255, 128, 0)
single = (42,) # Note the trailing comma — without it, (42) is just the number 42
empty = ()You can also create tuples without parentheses (tuple packing):
coordinates = 10, 20, 30 # This is a tuple: (10, 20, 30)Indexing and slicing work exactly like lists:
print(point[0]) # 3
print(rgb[-1]) # 0
print(rgb[1:]) # (128, 0)But you cannot assign to an index:
point[0] = 99 # TypeError: 'tuple' object does not support item assignmentOne of the most useful features in Python. You can assign each element to a separate variable in one line:
point = (3, 4)
x, y = point
print(x) # 3
print(y) # 4
# Works great with functions that return multiple values
def get_dimensions():
return 1920, 1080
width, height = get_dimensions()You can also use * to capture the rest:
first, *rest = [1, 2, 3, 4, 5]
print(first) # 1
print(rest) # [2, 3, 4, 5]
head, *middle, tail = [1, 2, 3, 4, 5]
print(head) # 1
print(middle) # [2, 3, 4]
print(tail) # 5| Use a list when... | Use a tuple when... |
|---|---|
| The data will change (add/remove items) | The data is fixed and shouldn't change |
| Items are the same "kind" of thing (all names, all scores) | Items have different roles (x/y coordinates, name/age pairs) |
| Order might change (sorting, shuffling) | You need it as a dictionary key (tuples are hashable, lists aren't) |
A good rule of thumb: if the collection is a "group of things," use a list. If it's a "record with fields," use a tuple.
If you find yourself using tuples as records, namedtuple from the collections module gives you named fields:
from collections import namedtuple
Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)
print(p.x) # 3
print(p.y) # 4
print(p[0]) # 3 — regular indexing still worksThis gives you the readability of a class with the simplicity of a tuple. We'll cover this more in a later lesson — just know it exists.
Check out example.py for a complete working example that demonstrates everything above.
Try the practice problems in exercises.py to test your understanding.
- Lists are ordered, mutable sequences created with
[]— use them when data will change - Tuples are ordered, immutable sequences created with
()— use them for fixed data - Both support indexing, negative indexing, and slicing with
[start:stop:step] sort()modifies in place (returnsNone);sorted()returns a new list- Assigning a list to a new variable does not copy it — use
[:],.copy(), orlist()for a shallow copy - For nested lists, use
copy.deepcopy()to avoid shared references - Tuple unpacking (
x, y = point) is one of Python's most useful features - Use
namedtuplewhen a tuple starts feeling like a record with named fields