Lists

The Course
•
January 7, 2026
•
7 min read

A list holds many values in order, and you can change it after you make it. It is the collection you will use most.

Warm-up: from Days 1 and 2

Three quick ones, from memory first. Scroll back only after you’ve tried.

1. Predict both lines.

Warm-up: predict Python
total = 0
for n in range(1, 5):
    if n % 2 == 0:
        total += n
print(total)
print("even" if total % 2 == 0 else "odd")
Check your prediction
6
even

range(1, 5) is 1, 2, 3, 4 — the 5 is excluded — and only 2 and 4 pass the test.

2. Fix. Three of these ratings are 4 or more, but this prints 0.

Warm-up: fix Python
ratings = [5, 3, 4, 4, 2]
good = 0
for r in ratings:
    if r >= 4:
        good + 1
print(good)
Check your fix
ratings = [5, 3, 4, 4, 2]
good = 0
for r in ratings:
    if r >= 4:
        good += 1
print(good)
3

good + 1 works out a new number and throws it away — the same mistake as calling strip() without keeping the result. += stores it back.

3. Write. Print the numbers from 10 down to 0 in steps of 2, on one line: 10 8 6 4 2 0.

Warm-up: write Python
# Your code here
Check your answer
for n in range(10, -1, -2):
    print(n, end=" ")
print()
10 8 6 4 2 0

The stop is excluded, so to include 0 when counting down you stop at -1. range(10, 0, -2) would end at 2.

Making and reading a list

Making and reading a list Python
scores = [88, 72, 95, 61]
mixed = ["Ada", 36, True, 3.5]     # types can be mixed, though rarely should be
empty = []

print(scores)
print(scores[0], scores[-1])       # first and last
print(scores[1:3])                 # a slice - same rules as strings
print(len(scores))
print(95 in scores)

Indexing and slicing work exactly as they did for strings on Day 1 — zero-based, negatives count from the end, slices exclude the stop.

Lists can be changed

This is the big difference from strings. A list is mutable: you can replace, add and remove items in place.

Changing a list Python
tasks = ["write", "test"]

tasks.append("ship")            # add one to the end
print(tasks)

tasks.insert(0, "plan")         # add at a position
print(tasks)

tasks.extend(["deploy", "rest"]) # add several
print(tasks);


tasks[1] = "WRITE"              # replace by index
removed = tasks.pop()           # remove and return the last
tasks.remove("test")            # remove the first match by value

print(tasks, "| removed:", removed)
append versus extend

append adds its argument as a single item — append([1, 2]) gives you a list inside your list. extend adds each item of what you give it. When the result surprises you, this is usually why.

Sorting

sort() reorders the list itself and returns nothing. sorted() leaves the original alone and hands back a new list. Choosing the wrong one is a classic bug.

sort versus sorted Python
scores = [88, 72, 95, 61]

print(sorted(scores))          # a new sorted list
print(scores)                  # original untouched

scores.sort()
print(scores)                  # now the original is sorted

scores.sort(reverse=True)
print(scores)

# key= decides what to sort by.
words = ["banana", "fig", "cherry"]
print(sorted(words, key=len))
scores = scores.sort() destroys your data

sort() returns None, so assigning its result replaces your list with nothing. If you want a sorted copy, use sorted().

Looping over lists

Everyday list loops Python
prices = [4.99, 12.50, 3.25, 8.00]

for price in prices:
    print(f"${price:.2f}")

print("Total:", sum(prices))

# Building a new list from an old one.
with_tax = []
for price in prices:
    with_tax.append(round(price * 1.2, 2))  # rounds the total price to two decimal places

print(with_tax)

That last pattern — start empty, loop, append — is so common that Python has a one-line form for it. You will meet comprehensions on Day 4.

Copying, and the trap

Assigning a list does not copy it. Both names end up pointing at the same list, and a change through one is visible through the other.

Two names, one list Python
original = [1, 2, 3]
alias = original           # NOT a copy
alias.append(4)

print("original:", original)
print("alias:   ", alias)
print("same object?", original is alias)

copy = original[:]         # a real copy (or list(original), or original.copy())
copy.append(5)

print("original:", original)
print("copy:    ", copy)

This is not a wart — it is the same rule every language with references has, and it is what makes passing large lists to functions cheap. You simply have to know it is happening.

Nested lists

A list can hold lists, which is how you get grids and tables.

A grid Python
grid = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9],
]

print(grid[1])        # the middle row
print(grid[1][2])     # row 1, column 2

for row in grid:
    for value in row:
        print(f"{value:3}", end="")     # takes at least 3 characters wide and do not move to the next line
    print()                             # moves to the next line

Before you write your own

Predict. Three lines. One of them depends on the section about copying.

What will this print? Python
cart = ["milk", "eggs"]
basket = cart
basket.append("bread")

print(len(cart))
print(cart[-1])
print(sorted(cart)[0])
Check your prediction
3
bread
bread

basket = cart doesn’t copy anything, so appending to basket changed the one list both names point at. And sorted puts "bread" first alphabetically.

Fix. This should print the prices from cheapest to dearest. It prints None.

Where did the prices go? Python
prices = [9.99, 2.50, 4.00]
prices = prices.sort()
print(prices)
Check your fix
prices = [9.99, 2.50, 4.00]
prices.sort()
print(prices)
[2.5, 4.0, 9.99]

sort() sorts the list where it is and returns None — so assigning its result replaced the list with nothing. Either call prices.sort() on its own, or write prices = sorted(prices).

Exercise

Score report

From the list of scores, print the count, the highest, the lowest, the average to one decimal place, and the three highest scores in descending order. Your labels can differ, but the numbers should be:

Count:   7
Highest: 95
Lowest:  61
Average: 81.7
Top 3:   [95, 93, 88]
scores = [88, 72, 95, 61, 79, 93, 84]

# Your code here
Hint 1: the idea

Python has a built-in for most of this: len, max, min and sum. The average is a sum divided by a count. The top three is two steps: put the scores in descending order, then take the first three.

Hint 2: a first step

Start with print(len(scores), max(scores), min(scores)) to see the easy three. For the top three, sorted(scores, reverse=True) gives a new list, highest first — and a slice takes the front of any list.

Show one solution
scores = [88, 72, 95, 61, 79, 93, 84]

print(f"Count:   {len(scores)}")
print(f"Highest: {max(scores)}")
print(f"Lowest:  {min(scores)}")
print(f"Average: {sum(scores) / len(scores):.1f}")
print(f"Top 3:   {sorted(scores, reverse=True)[:3]}")

sorted(...)[:3] — sort a copy descending, then slice the first three. Using sorted rather than sort leaves the original list in its original order, which the rest of a program may depend on.

What you learned

  • Lists are ordered, indexable, sliceable and mutable.
  • append, insert, extend, pop and remove change a list in place.
  • sort() reorders in place and returns None; sorted() returns a new list.
  • key= controls what sorting compares.
  • Assignment shares a list; [:], list() or .copy() make a real copy.
Last updated on January 7, 2026

Was this article helpful?

Your response is saved on this device.