Lists in Python

💡 Big Idea

A list is an ordered collection of values stored under a single variable name. Instead of creating ten separate variables to hold ten scores, you create one list that holds all ten. Lists can be looped over, sliced, sorted, and modified — they are the foundation of almost every program that handles more than one piece of related data.

1   Why Do We Need Lists?

Imagine you are writing a program to track the scores of five students. Without lists, you might write:

score1 = 85
score2 = 42
score3 = 91
score4 = 67
score5 = 55

This works for five students. But what about thirty? Or three hundred? And how would you loop over them, sort them, or find the highest? You would need separate code for every single variable.

🔍 Analogy: A Playlist

A music playlist is a list. It has a name ("Workout Mix"), it holds multiple items (songs), you can add to it, remove from it, reorder it, and play it from start to finish. Each song is accessible by its position in the playlist. A Python list works exactly the same way — it is a named, ordered collection you can manipulate as a whole.

With a list, all five scores live together under one name:

scores = [85, 42, 91, 67, 55]

Now you can loop over them, sort them, slice them, and pass them to a function — all in one step.

2   Creating a List

A list is written with square brackets [ ], with items separated by commas. You can put any type of value inside — integers, floats, strings, booleans, or even a mix.

scores     = [85, 42, 91, 67, 55]           # integers
prices     = [9.99, 14.50, 3.75]             # floats
names      = ["Alice", "Bob", "Carol"]       # strings
flags      = [True, False, True, True]       # booleans
mixed      = [1, "hello", 3.14, True]        # mixed types (valid, but unusual)
empty      = []                              # an empty list — valid and useful
💜 Name your list with a plural noun

A list named scores immediately tells you it holds multiple scores. A list named score is confusing — it sounds like a single value. The convention is plural nouns for lists: names, prices, students, results. This makes your code read naturally, especially inside loops: for student in students: reads like English.

2.1   The Vocabulary of Lists

TermWhat it meansExample
ListAn ordered, mutable collection of values.[10, 20, 30]
Element / ItemA single value stored inside the list.20 is an element of [10, 20, 30]
IndexThe position of an element, starting at 0.20 is at index 1
LengthThe number of elements in the list.len([10, 20, 30])3
MutableCan be changed after creation — items can be added, removed, or replaced.Unlike strings, lists can be modified in place.
OrderedItems stay in the order you put them. Position matters.[1, 2, 3][3, 2, 1]

3   Accessing Items

List indexing works identically to string indexing from Article 7 — zero-based, supports negative indices, and raises an IndexError if you go out of range.

fruits = ["apple", "banana", "cherry", "date", "elderberry"]
#           0         1          2        3          4

print(fruits[0])    # apple      — first item
print(fruits[2])    # cherry     — index 2
print(fruits[-1])   # elderberry — last item
print(fruits[-2])   # date       — second to last

Slicing also works exactly as in Article 7:

print(fruits[1:3])    # ['banana', 'cherry']
print(fruits[:2])     # ['apple', 'banana']
print(fruits[-2:])    # ['date', 'elderberry']
print(fruits[::-1])   # ['elderberry', 'date', 'cherry', 'banana', 'apple']

3.1   Checking the Length

The built-in len() function returns the number of items in a list. You will use this constantly — in loops, in conditions, in slice calculations:

scores = [85, 42, 91, 67, 55]
print(len(scores))         # 5

# The last valid index is always len(list) - 1
print(scores[len(scores) - 1])    # 55 — same as scores[-1]
✅ Check Your Understanding
  1. What is the index of the first element in any Python list?
  2. Given animals = ["cat", "dog", "rabbit", "hamster"], what is the value of:

    animals[1]
    animals[-1]
    animals[1:3]
    len(animals)
  3. A list has 7 items. What is the index of the last item? What happens if you try my_list[7]?
  4. Write a line of code that prints the middle item of any odd-length list called items — without knowing its length in advance. (Hint: use len() and integer division //.)

4   Modifying a List

Unlike strings, lists are mutable — you can change them after creation. Python gives you several ways to modify a list: replacing items, adding new ones, and removing existing ones.

4.1   Replacing an Item

Use the index on the left side of an assignment to replace a specific item:

scores = [85, 42, 91, 67, 55]
scores[1] = 78            # replace the item at index 1
print(scores)             # [85, 78, 91, 67, 55]

4.2   Adding Items

MethodWhat it doesExampleResult
.append(item)Adds one item to the end of the list.nums.append(99)[1, 2, 3, 99]
.insert(index, item)Inserts one item at a specific position. Everything after shifts right.nums.insert(1, 99)[1, 99, 2, 3]
.extend(other_list)Adds all items from another list onto the end.nums.extend([7, 8])[1, 2, 3, 7, 8]
fruits = ["apple", "banana"]

fruits.append("cherry")
print(fruits)               # ['apple', 'banana', 'cherry']

fruits.insert(1, "avocado")
print(fruits)               # ['apple', 'avocado', 'banana', 'cherry']

fruits.extend(["date", "elderberry"])
print(fruits)               # ['apple', 'avocado', 'banana', 'cherry', 'date', 'elderberry']
🟠 Common Confusion: append vs extend

append() adds its argument as a single item — even if you pass a list. fruits.append(["kiwi", "lime"]) gives you ["apple", ["kiwi", "lime"]] — a list inside a list, which is almost never what you want.

extend() unpacks the argument and adds each item individually. Use append() to add one thing; use extend() to merge two lists together.

4.3   Removing Items

Method / KeywordWhat it doesExample
.remove(value)Removes the first occurrence of a specific value. Raises ValueError if not found.fruits.remove("banana")
.pop()Removes and returns the last item.last = fruits.pop()
.pop(index)Removes and returns the item at a specific index.second = fruits.pop(1)
del list[index]Deletes the item at a specific index. Does not return it.del fruits[0]
.clear()Removes all items, leaving an empty list.fruits.clear()
colours = ["red", "green", "blue", "green", "yellow"]

colours.remove("green")       # removes the FIRST "green"
print(colours)                # ['red', 'blue', 'green', 'yellow']

last = colours.pop()
print(last)                   # yellow
print(colours)                # ['red', 'blue', 'green']

del colours[0]
print(colours)                # ['blue', 'green']
⚠️ remove() only removes the first match

If your list contains duplicate values, .remove("green") only removes the first one it finds. If you need to remove all occurrences, you will need a loop. If the value is not in the list at all, Python raises a ValueError — use in to check before removing if you are not sure.

✅ Check Your Understanding
  1. What is the difference between .append() and .extend()? Give an example where using the wrong one would cause a bug.
  2. Trace this code step by step. What does items contain after each line?

    items = [10, 20, 30, 40, 50]
    items.append(60)
    items.insert(0, 5)
    items.remove(30)
    last = items.pop()
    print(items)
    print(last)
  3. What is the difference between .pop() and del? When would you prefer one over the other?
  4. A student writes my_list.remove("banana") but the list does not contain "banana". What happens? How would you write this safely?

5   Looping Over Lists

Looping and lists were made for each other. A for loop steps through every item in a list automatically — no index needed, no counter to manage.

5.1   The Basic for Loop

names = ["Alice", "Bob", "Carol"]

for name in names:
    print("Hello,", name + "!")

Output:

Hello, Alice!
Hello, Bob!
Hello, Carol!

5.2   When You Need the Index Too: enumerate()

Sometimes you need both the item and its position. Rather than using range(len(list)), Python provides the cleaner built-in enumerate(), which gives you both at once:

students = ["Alice", "Bob", "Carol", "Dan"]

# Without enumerate — works but clunky
for i in range(len(students)):
    print(i + 1, students[i])

# With enumerate — cleaner and more readable
for i, student in enumerate(students, start=1):
    print(i, student)

Both produce the same output:

1 Alice
2 Bob
3 Carol
4 Dan
💜 When to use enumerate vs a plain for loop

If you only need the values — use for item in list. If you need both the position and the value — use enumerate(). Reaching for range(len(list)) when a plain for loop would do is a common beginner habit worth breaking early.

5.3   Building a List Inside a Loop

One of the most common patterns is starting with an empty list and building it up inside a loop — the collector pattern from Article 5:

scores = [78, 92, 55, 84, 40, 67, 38, 95]
passing = []

for score in scores:
    if score >= 60:
        passing.append(score)

print("Passing scores:", passing)
print("Pass rate:", len(passing), "out of", len(scores))
✅ Check Your Understanding
  1. What does enumerate() give you that a plain for item in list does not?
  2. Predict the output of this program:

    prices = [12.0, 8.5, 22.0, 5.0, 15.0]
    expensive = []
    for price in prices:
        if price > 10:
            expensive.append(price)
    print(expensive)
  3. Rewrite this loop using enumerate() so that it prints "1. Alice", "2. Bob", etc.:

    names = ["Alice", "Bob", "Carol"]
    for i in range(len(names)):
        print(str(i + 1) + ". " + names[i])
  4. Write a loop that goes through a list of integers and builds a new list containing only the even numbers.

6   Useful List Methods and Functions

Python gives lists a rich set of built-in methods. Here are the ones you will use most often:

6.1   Sorting

scores = [85, 42, 91, 67, 55]

scores.sort()                     # sorts IN PLACE — modifies the original list
print(scores)                     # [42, 55, 67, 85, 91]

scores.sort(reverse=True)         # sorts in descending order
print(scores)                     # [91, 85, 67, 55, 42]

# sorted() returns a NEW sorted list — original is unchanged
original = [85, 42, 91, 67, 55]
ranked = sorted(original)
print(original)                   # [85, 42, 91, 67, 55] — unchanged
print(ranked)                     # [42, 55, 67, 85, 91]
🟠 Common Confusion: .sort() vs sorted()

.sort() modifies the list in place and returns None. Writing ranked = scores.sort() is a common mistake — ranked will be None, not a sorted list.

sorted() leaves the original list untouched and returns a brand-new sorted list. Use .sort() when you are happy to modify the original; use sorted() when you need to keep the original intact.

6.2   Searching

fruits = ["apple", "banana", "cherry", "banana"]

# Check membership
print("banana" in fruits)         # True
print("mango" in fruits)          # False

# Find the index of the first occurrence
print(fruits.index("banana"))     # 1

# Count how many times a value appears
print(fruits.count("banana"))     # 2
⚠️ .index() raises ValueError if not found

If you call fruits.index("mango") and "mango" is not in the list, Python raises a ValueError. Always check with in first if you are not certain the item exists.

6.3   Other Handy Built-ins

Function / MethodWhat it returnsExampleResult
len(list)Number of items.len([1, 2, 3])3
sum(list)Sum of all numeric items.sum([10, 20, 30])60
min(list)The smallest item.min([3, 1, 4, 1, 5])1
max(list)The largest item.max([3, 1, 4, 1, 5])5
.reverse()Reverses the list in place. Returns None.nums.reverse()List is reversed.
.copy()Returns a shallow copy of the list.backup = nums.copy()New list, same contents.
✅ Check Your Understanding
  1. What is the difference between .sort() and sorted()? When would you choose each?
  2. A student writes result = scores.sort() and then uses result. What is the value of result, and why?
  3. Given nums = [4, 7, 2, 9, 1, 5], what is the output of:

    print(min(nums))
    print(max(nums))
    print(sum(nums))
    print(nums.index(9))
  4. Write three lines of code that: start with a list of scores, calculate the average (using sum() and len()), and print it rounded to one decimal place.

7   Copying Lists — A Subtle Trap

This section covers one of the most surprising behaviours beginners encounter with lists. Read it carefully — it has caught out almost every programmer at least once.

7.1   Assignment Does Not Copy a List

When you write b = a for a simple variable, b gets its own copy of the value. Lists do not work this way. When you write b = a for a list, b and a both point to the same list in memory. Changing one changes the other:

original = [1, 2, 3, 4, 5]
alias = original              # NOT a copy — both point to the same list

alias.append(6)
print(original)               # [1, 2, 3, 4, 5, 6] — also changed!
print(alias)                  # [1, 2, 3, 4, 5, 6]
🔍 Analogy: A Shared Document

Imagine two people editing the same Google Doc. They each have their own screen, but they are looking at the same document — not separate copies. Any change one person makes appears on the other's screen immediately. alias = original is the same situation: two names, one list. To get a truly independent copy — your own separate document — you need to explicitly make one.

7.2   How to Actually Copy a List

There are three common ways to make a true independent copy:

original = [1, 2, 3, 4, 5]

copy_a = original.copy()      # using .copy()
copy_b = original[:]          # using a full slice
copy_c = list(original)       # using the list() constructor

copy_a.append(99)
print(original)               # [1, 2, 3, 4, 5] — unchanged
print(copy_a)                 # [1, 2, 3, 4, 5, 99]

All three methods produce an independent shallow copy. Any of them is fine — .copy() is the most readable and self-documenting.

✅ Check Your Understanding
  1. Why does b = a not create an independent copy of a list?
  2. Predict the output:

    x = [10, 20, 30]
    y = x
    y[0] = 99
    print(x)
    print(y)
  3. Fix the code above so that changing y does not affect x.
  4. Write the three different ways to copy a list and explain in one sentence when you would use .copy() over [:].

8   Lists of Lists

A list can contain any value — including other lists. This is called a nested list, and it is a natural way to represent grid-like or tabular data: rows of a table, a game board, a matrix of values.

# A class's scores — three students, three tests each
results = [
    [85, 90, 78],    # Alice's scores
    [42, 55, 61],    # Bob's scores
    [91, 88, 95],    # Carol's scores
]

# Access a whole row (one student's scores)
print(results[0])           # [85, 90, 78]

# Access a single score — row then column
print(results[0][1])        # 90  (Alice's second test)
print(results[2][0])        # 91  (Carol's first test)

The notation results[row][column] reads left to right: first pick the row, then pick the item within that row.

8.1   Looping Over a Nested List

results = [
    ["Alice", 85, 90, 78],
    ["Bob",   42, 55, 61],
    ["Carol", 91, 88, 95],
]

for row in results:
    name   = row[0]
    scores = row[1:]           # slice off the name, keep the scores
    avg    = sum(scores) / len(scores)
    print(f"  {name:<8} average: {avg:.1f}")

Output:

  Alice    average: 84.3
  Bob      average: 52.7
  Carol    average: 91.3
✅ Check Your Understanding
  1. Given this nested list:

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

    What is the value of grid[1][2]? What about grid[2][0]?

  2. Write a loop that prints each row of grid on its own line.
  3. Write a loop that calculates and prints the sum of each row in grid.

9   Putting It All Together

Here is a complete program that uses the most important list operations from this article. Read it carefully and predict the output before running it.

def get_scores(n):
    """Collects n scores from the user and returns them as a list."""
    scores = []
    for i in range(1, n + 1):
        while True:
            score = int(input(f"  Enter score {i} of {n}: "))
            if 0 <= score <= 100:
                break
            print("  Please enter a score between 0 and 100.")
        scores.append(score)
    return scores

def analyse(scores):
    """Returns a dict of summary statistics for a list of scores."""
    passing = [s for s in scores if s >= 60]
    return {
        "count":   len(scores),
        "highest": max(scores),
        "lowest":  min(scores),
        "average": round(sum(scores) / len(scores), 1),
        "passing": len(passing),
    }

def print_report(scores, stats):
    """Prints a formatted report."""
    ranked = sorted(scores, reverse=True)

    print("\n=== Score Report ===")
    print(f"  Scores entered : {scores}")
    print(f"  Ranked (high→low): {ranked}")
    print(f"  Highest : {stats['highest']}")
    print(f"  Lowest  : {stats['lowest']}")
    print(f"  Average : {stats['average']}")
    print(f"  Passing : {stats['passing']} / {stats['count']}")


# ── Main ──────────────────────────────────────────────────────────
print("Enter 5 student scores:")
scores = get_scores(5)
stats  = analyse(scores)
print_report(scores, stats)

Things to notice:

  • get_scores() builds a list using the collector pattern with .append() inside a loop.
  • analyse() uses a list comprehension ([s for s in scores if s >= 60]) — a compact way to build a filtered list. This is introduced properly in a later article, but you can read it as: "a list of every s in scores where s >= 60."
  • sorted(scores, reverse=True) creates a ranked copy without modifying scores — so the original order is preserved for the report.
  • The functions communicate entirely through parameters and return values — no global lists are modified.

10   Final Check: The Big Picture

💡 Big Idea

A list is an ordered, mutable collection of values stored under one name. Items are accessed by index (zero-based, negative indices count from the end). Lists can be modified with .append(), .insert(), .remove(), and .pop(). for loops and enumerate() step through them naturally. .sort() modifies in place; sorted() returns a new list. Assigning a list to a new variable does not copy it — use .copy() or [:]. Lists can contain other lists for tabular data.

✅ Final Check Your Understanding
  1. In your own words: what is a list, and why is it more useful than creating a separate variable for each value?
  2. Trace this program completely. Write the value of data after every line.

    data = [3, 1, 4, 1, 5, 9]
    data.append(2)
    data.remove(1)
    data.sort()
    last = data.pop()
    print(data)
    print(last)
  3. This program is supposed to print a copy of scores sorted from highest to lowest, while keeping scores in its original order. It has a bug — find it and fix it.

    scores = [78, 92, 55, 84, 67]
    ranked = scores.sort(reverse=True)
    print("Original:", scores)
    print("Ranked:  ", ranked)
  4. Write a function called above_average(numbers) that takes a list of numbers and returns a new list containing only those that are strictly above the average. Test it on [60, 80, 70, 90, 50] — the expected result is [80, 90].
  5. Explain what this line does, and what value backup holds after it runs:

    scores = [85, 42, 91]
    backup = scores
    scores.clear()
    print(backup)

    What should you write instead to make backup an independent copy?

11   Quick-Reference Summary

OperationSyntaxNotes
Createitems = [1, 2, 3]Square brackets, comma-separated.
Access by indexitems[0], items[-1]Zero-based. Negative counts from end.
Sliceitems[1:3], items[::-1]Same notation as strings. Returns new list.
Lengthlen(items)Number of items.
Replace itemitems[i] = new_valueLists are mutable — strings are not.
Add to enditems.append(x)One item. Use extend() for multiple.
Insert at positionitems.insert(i, x)Shifts everything after it right.
Remove by valueitems.remove(x)First occurrence only. ValueError if absent.
Remove by indexitems.pop(i), del items[i]pop() returns the removed item.
Sort in placeitems.sort()Modifies original. Returns None.
Sort to new listsorted(items)Original unchanged. Returns new sorted list.
Searchx in items, items.index(x)index() raises ValueError if not found.
Sum / Min / Maxsum(items), min(items), max(items)Built-in functions — work on numeric lists.
Copyitems.copy() or items[:]b = a is NOT a copy — both point to the same list.
Loopfor item in items:Use enumerate(items) if you also need the index.