Index Ai Academy
Phase 0 · Tools · Python core

Lists

Keep many values in order, then index, slice and change them.

About 20 minutes · Free lesson

Start this lesson

Why it matters

Data comes in collections. Rows from an export. A batch of headlines to score. The documents you'll search in a RAG system. The examples a model trains on. And when you call an LLM, the conversation you send is literally a list of messages. Lists are Python's usual way to hold many things in order.

What you'll be able to do

  • Create a list and read items and slices from it by position
  • Add, change and remove items
  • Summarize a list with len, sum, min, max and sorted
  • Explain why two names can point at the same list, and make a real copy

What it covers

  1. Many values, in order
  2. Indexing and slicing
  3. Changing a list
  4. Asking questions about a list
  5. Two names, one list
  6. Worked example: a week of signups
  7. Methods that change the list, and methods that don't
  8. A list can hold anything

Many values, in order

Think of a shopping list: several items, written in order, one after another. A Python list is the same. It holds several values in a fixed order. Write it with square brackets and commas:

channels = ["search", "display", "video", "email", "social"]
daily_clicks = [412, 388, 501, 467, 590]
empty = []

print(channels)
print(len(channels))      # how many items
print(len(empty))
It runs right here in your browser, with nothing to install.

A list can hold any type of value, even a mix. In practice, most lists hold one kind of thing: all names, or all numbers. Lists are often built from other data, as you saw with .split() in the strings lesson.

Indexing and slicing

Lists use the same positions as strings:

  • the first item is at index 0,
  • the last is at -1,
  • a slice [start:stop] stops before stop.

The difference is that each position holds a whole value, not a single character.

Try it now

Same rules as strings: a slice stops before the stop index, and negative indexes count from the end. A slice of a list is a new, shorter list.

channels = ["search", "display", "video", "email", "social"]
print(channels[0])
print(channels[-1])
print(channels[1:3])
print(channels[:2])

As with strings, asking for a position that doesn't exist, such as channels[5] here, raises an IndexError.

Quick check
What does this print?
channels = ["search", "display", "video", "email", "social"]
print(channels[-2])
  1. email
  2. video
  3. social
  4. display
Show the answer

email

-1 is the last item, 'social', so -2 is the one before it: email.

Changing a list

Strings can't be changed in place, but lists can. You can replace an item by giving its index a new value. And you can add or remove items with methods. These methods change the list itself, so there's no need to store a result.

CodeEffect
items[0] = "paid search"replace the item at index 0
items.append(x)add x to the end
items.insert(0, x)add x at index 0, shifting the rest along
items.extend(other_list)add every item from another list
items.remove(x)remove the first item equal to x
items.pop()remove the last item and give it back (pop(0) takes the first)
queue = ["Spring sale", "New arrivals"]
queue.append("Customer stories")
queue.insert(0, "Welcome email")
print(queue)

queue[1] = "Spring sale (final)"
queue.remove("New arrivals")
sent = queue.pop(0)
print("Sent:", sent)
print("Still queued:", queue)

.remove() raises a ValueError if the item isn't in the list.

Asking questions about a list

daily_spend = [310.5, 288.0, 402.75, 365.2, 290.0]

print("Days:", len(daily_spend))
print("Total:", sum(daily_spend))
print("Lowest:", min(daily_spend), "Highest:", max(daily_spend))
print("Average:", round(sum(daily_spend) / len(daily_spend), 2))

tags = ["seo", "email", "ads"]
print("email" in tags)          # True or False
print(tags.count("seo"))        # how many times it appears
print(tags.index("ads"))        # where it first appears

sum() only works on numbers. min() and max() also work on strings, comparing them in alphabetical order.

Sorting

There are two ways to sort, and the difference catches everyone at least once:

  • sorted(items) gives you a new sorted list and leaves the original alone.
  • items.sort() sorts the list in place and returns None (Python's value for "nothing").

Both accept reverse=True for largest-first order.

scores = [72, 95, 64, 88]
ranked = sorted(scores, reverse=True)
print(ranked)
print(scores)        # unchanged

scores.sort()
print(scores)        # now changed

result = scores.sort()
print(result)        # None: never write scores = scores.sort()
Quick check
What does this print?
scores = [72, 95, 64]
top = sorted(scores, reverse=True)
print(scores[0], top[0])
  1. 72 72
  2. 72 95
  3. 95 95
  4. 64 95
Show the answer

72 95

sorted() gives back a new list and leaves scores alone, so scores[0] is still 72. The new list top starts with the largest, 95.

Two names, one list

Giving a list a second name does not copy it. Picture one shared document with two people's names on it: an edit by either person shows up for both. Both names refer to the same list, so a change through one name shows up through the other:

approved = ["Headline A", "Headline B"]
backup = approved                 # same list, two names
backup.append("Headline C")
print(approved)                   # it changed too

real_copy = approved.copy()       # a separate list with the same items
real_copy.append("Headline D")
print(approved)                   # unchanged this time
print(real_copy)

This is different from the numbers and strings you've used so far. You can't change those in place, so sharing them between names never causes surprises. Lists can change in place, so sharing matters. When you want a separate list, use .copy() (or the slice [:]).

Lists inside lists

An item can itself be a list. That's a natural way to hold a table, with one inner list per row. Use two indexes: the first picks the row, and the second picks the item in it.

rows = [
    ["cmp_101", "search", 5000],
    ["cmp_102", "display", 2000],
]
print(rows[1])
print(rows[1][2])
Worked example

Worked example: a week of signups

Signups from a landing page, Monday to Sunday. Compare weekdays with the weekend and find the best day.

signups = [42, 38, 51, 47, 60, 22, 19]   # Monday to Sunday

weekdays = signups[:5]
weekend = signups[5:]

print("Total:", sum(signups))
print("Best day:", max(signups))
print("Weekday average:", sum(weekdays) / len(weekdays))
print("Weekend average:", sum(weekend) / len(weekend))
print("High to low:", sorted(signups, reverse=True))
print("Order kept:", signups)
In the lesson you can change this and run it.

Slicing splits the week into two new lists without changing signups. Average is always sum / len, so the same expression works for any length of list. Using sorted() instead of .sort() leaves the day order intact, which matters here because position 0 means Monday. Sorting in place would have lost which number belonged to which day.

Quick check
What does this print?
a = [1, 2, 3]
b = a
b.append(4)
print(len(a))
  1. 4
  2. None
  3. 3
  4. An error
Show the answer

4

a and b are two names for the same list, so appending through b makes that one list [1, 2, 3, 4]. Its length, read through a, is 4.

Methods that change the list, and methods that don't

Some list operations change the list and give back nothing. Others leave it alone and give back a new one. Mixing them up causes one of the most common bugs in Python.

scores = [3, 1, 2]

result = scores.sort()     # changes scores, gives back None
print(scores, result)

scores = [3, 1, 2]
result = sorted(scores)    # leaves scores alone, gives back a new list
print(scores, result)

So x = x.sort() throws your list away and leaves x holding None. Use x.sort() on its own line, or x = sorted(x). The same applies to .append(), .remove() and .reverse(): call them on their own line and don't assign the result.

append, plus and remove

.append() adds one item to this list. a + b builds a new list and leaves both originals alone. If you write a + [5] and never save it, nothing changes.

.remove() takes a value, not a position. items.remove(2) removes the first item equal to 2, wherever it is. To remove by position, use .pop(2), which also hands you the item it took out.

Getting a real copy

b = a doesn't copy anything: it gives the same list a second name, as you saw above. When you need an independent list, ask for one with b = a.copy() or b = list(a).

This matters most when you pass lists around later in the course. If a part of your program quietly changes a list that another part is still using, the bug can appear far from its cause. When two values change together and you can't see why, ask whether they're the same list.

A list can hold anything

A list can hold numbers and text together, and even other lists. Python won't object. Sorting a mixed list fails, however, since Python won't guess whether 3 comes before "apple". Keep one kind of item per list, and sorting stays easy and predictable.

Practice what you just read

  • 9 code examples you can edit and run
  • a practice exercise that checks your code
  • a 6-question quiz
  • 5 review cards for spaced repetition

Start this lesson

Try a quiz question

What does this print?

x = [3, 1, 2]
x = x.sort()
print(x)
  1. [1, 2, 3]
  2. None
  3. [3, 1, 2]
  4. An error

Answer it at the end of the lesson to see if you're right, and why.

Before this lesson