🎯 Learning Objectives
- Create lists and understand they are ordered, mutable, and allow duplicates
- Access elements with indexing and extract sublists with slicing
- Modify lists in place: add, remove, and change elements
- Use essential list methods:
append,extend,insert,pop,remove,sort - Iterate over lists with
forloops andenumerate() - Understand list copying, nested lists, and common patterns
What is a List?
A list is Python's most versatile data structure — an ordered, mutable collection that can hold items of any type (including mixed types).
# Creating lists
numbers = [1, 2, 3, 4, 5]
fruits = ["apple", "banana", "cherry"]
mixed = [42, "hello", 3.14, True, None]
empty = []
# Lists can contain other lists
matrix = [[1, 2], [3, 4], [5, 6]]
# list() constructor
letters = list("Python") # ['P', 'y', 't', 'h', 'o', 'n']
nums = list(range(5)) # [0, 1, 2, 3, 4]
create_lists.py
Indexing & Slicing
Lists use the same indexing and slicing rules as strings (zero-based, negative indices count from the end):
colors = ["red", "green", "blue", "yellow", "purple"]
# 0 1 2 3 4
# -5 -4 -3 -2 -1
# Indexing
print(colors[0]) # "red"
print(colors[-1]) # "purple"
# Slicing [start:stop:step]
print(colors[1:3]) # ["green", "blue"]
print(colors[:2]) # ["red", "green"]
print(colors[2:]) # ["blue", "yellow", "purple"]
print(colors[::2]) # ["red", "blue", "purple"]
print(colors[::-1]) # reversed list
indexing.py
Modifying Lists
Unlike strings, you can change list elements in place:
nums = [10, 20, 30, 40, 50]
# Change a single element
nums[0] = 99
print(nums) # [99, 20, 30, 40, 50]
# Change a slice (can even change length!)
nums[1:3] = [200, 300, 350]
print(nums) # [99, 200, 300, 350, 40, 50]
# Delete with del
del nums[0]
print(nums) # [200, 300, 350, 40, 50]
# Delete a slice
del nums[1:3]
print(nums) # [200, 40, 50]
modifying.py
Adding Elements
| Method | Action | Returns |
|---|---|---|
.append(x) | Add x to the end | None (modifies in place) |
.insert(i, x) | Insert x at index i | None |
.extend(iterable) | Add all items from iterable | None |
+ operator | Concatenate two lists | New list |
* operator | Repeat a list | New list |
fruits = ["apple", "banana"]
# append — one item at the end
fruits.append("cherry")
print(fruits) # ["apple", "banana", "cherry"]
# insert — at a specific position
fruits.insert(1, "avocado")
print(fruits) # ["apple", "avocado", "banana", "cherry"]
# extend — merge another list
fruits.extend(["date", "elderberry"])
print(fruits) # ["apple", "avocado", "banana", "cherry", "date", "elderberry"]
# Concatenation (creates a NEW list)
more = fruits + ["fig", "grape"]
# Repetition
zeros = [0] * 5 # [0, 0, 0, 0, 0]
adding.py
append() vs extend(): .append([1,2]) adds the
list as a single element. .extend([1,2]) adds each item
individually. This is a common source of bugs.
Removing Elements
| Method | Action | Returns |
|---|---|---|
.pop(i) | Remove & return item at index i (default: last) | The removed item |
.remove(x) | Remove first occurrence of value x | None |
.clear() | Remove all items | None |
del list[i] | Delete item at index | — |
items = ["a", "b", "c", "d", "e"]
# pop — remove by index, returns the item
last = items.pop() # "e", items = ["a", "b", "c", "d"]
second = items.pop(1) # "b", items = ["a", "c", "d"]
# remove — remove by value (first occurrence only)
items.remove("c") # items = ["a", "d"]
# clear — empty the list
items.clear() # items = []
removing.py
.remove(x) raises a ValueError if x is not in the list.
Check with if x in list: first, or use a try/except.
Sorting & Searching
nums = [3, 1, 4, 1, 5, 9, 2, 6]
# sort() — modifies in place, returns None
nums.sort()
print(nums) # [1, 1, 2, 3, 4, 5, 6, 9]
# Descending order
nums.sort(reverse=True)
print(nums) # [9, 6, 5, 4, 3, 2, 1, 1]
# sorted() — returns a NEW sorted list (original unchanged)
original = [3, 1, 4, 1, 5]
ordered = sorted(original)
print(original) # [3, 1, 4, 1, 5] (unchanged)
print(ordered) # [1, 1, 3, 4, 5]
# Sort strings by length (custom key)
words = ["banana", "pie", "strawberry", "kiwi"]
words.sort(key=len)
print(words) # ["pie", "kiwi", "banana", "strawberry"]
# reverse() — reverse in place
nums = [1, 2, 3]
nums.reverse()
print(nums) # [3, 2, 1]
sorting.py
# Searching
fruits = ["apple", "banana", "cherry", "banana"]
print("banana" in fruits) # True
print(fruits.index("cherry")) # 2 (first occurrence)
print(fruits.count("banana")) # 2
# index() raises ValueError if not found — check first!
if "mango" in fruits:
pos = fruits.index("mango")
searching.py
sorted() when you need a sorted copy and want to keep the original.
Use .sort() when you're done with the original order — it's slightly faster
(no extra memory allocation).
Iterating Over Lists
colors = ["red", "green", "blue"]
# Basic for loop
for color in colors:
print(color)
# With index — use enumerate()
for i, color in enumerate(colors):
print(f"{i}: {color}")
# 0: red
# 1: green
# 2: blue
# Start enumerate at a different number
for i, color in enumerate(colors, start=1):
print(f"{i}. {color}")
# 1. red
# 2. green
# 3. blue
iteration.py
range(len(list)) unless you truly need only
the index. Use enumerate() when you need both index and value — it's more
Pythonic and less error-prone.
Copying Lists
Assigning a list to a new variable does not copy it — both names refer to the same object:
# This is NOT a copy — it's an alias
a = [1, 2, 3]
b = a
b.append(4)
print(a) # [1, 2, 3, 4] — a was also modified!
# Shallow copy methods (all equivalent)
c = a.copy()
c = a[:]
c = list(a)
c.append(5)
print(a) # [1, 2, 3, 4] — a is unchanged
print(c) # [1, 2, 3, 4, 5]
# Deep copy — needed for nested lists
import copy
nested = [[1, 2], [3, 4]]
deep = copy.deepcopy(nested)
deep[0][0] = 99
print(nested) # [[1, 2], [3, 4]] — original safe
print(deep) # [[99, 2], [3, 4]]
copying.py
copy.deepcopy() when nesting is involved.
Useful List Patterns
# Length, min, max, sum
nums = [4, 8, 2, 9, 1]
print(len(nums)) # 5
print(min(nums)) # 1
print(max(nums)) # 9
print(sum(nums)) # 24
# Unpacking
first, *rest = [1, 2, 3, 4, 5]
print(first) # 1
print(rest) # [2, 3, 4, 5]
a, b, *_ = [10, 20, 30, 40]
print(a, b) # 10 20
# zip — iterate multiple lists in parallel
names = ["Alice", "Bob", "Charlie"]
scores = [85, 92, 78]
for name, score in zip(names, scores):
print(f"{name}: {score}")
# List comprehension (preview — full coverage in Lesson 16)
squares = [x ** 2 for x in range(1, 6)]
print(squares) # [1, 4, 9, 16, 25]
patterns.py
Primary source: Python Docs — More on Lists
Ask your AI tutor! Confused about shallow vs deep copy? Unsure when
to use append vs extend? Lists are foundational — make sure
you're solid here before moving on.
💻 Exercises
Given a list of numbers [23, 45, 12, 67, 34, 89, 2], write a script
that prints: the length, sum, average (to 2 decimal places), minimum, and maximum.
Show solution
nums = [23, 45, 12, 67, 34, 89, 2]
print(f"Length: {len(nums)}")
print(f"Sum: {sum(nums)}")
print(f"Average: {sum(nums) / len(nums):.2f}")
print(f"Min: {min(nums)}")
print(f"Max: {max(nums)}")
# Length: 7, Sum: 272, Average: 38.86, Min: 2, Max: 89
Write code that takes [1, 2, 2, 3, 4, 4, 4, 5] and produces a new list
with duplicates removed, preserving the original order.
Don't use set() (that doesn't preserve order in older Python).
Show solution
original = [1, 2, 2, 3, 4, 4, 4, 5]
unique = []
for item in original:
if item not in unique:
unique.append(item)
print(unique) # [1, 2, 3, 4, 5]
# Alternative (Python 3.7+ — dict preserves insertion order)
unique2 = list(dict.fromkeys(original))
print(unique2) # [1, 2, 3, 4, 5]
Given a 2D list (matrix) [[1, 2, 3], [4, 5, 6], [7, 8, 9]],
flatten it into a single list [1, 2, 3, 4, 5, 6, 7, 8, 9].
Try both a nested loop approach and a list comprehension.
Show solution
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
# Approach 1: nested for loop
flat = []
for row in matrix:
for item in row:
flat.append(item)
print(flat) # [1, 2, 3, 4, 5, 6, 7, 8, 9]
# Approach 2: list comprehension
flat2 = [item for row in matrix for item in row]
print(flat2) # [1, 2, 3, 4, 5, 6, 7, 8, 9]
# Approach 3: extend in a loop
flat3 = []
for row in matrix:
flat3.extend(row)
print(flat3) # [1, 2, 3, 4, 5, 6, 7, 8, 9]