BlogLists, in more detail
2026-05-14 · By Kelvin Amoaba

Lists, in more detail

Counting from zero, slicing, adding things in, and the moment list comprehensions stop looking like magic.

A student emailed me on a Sunday evening, frustrated. She had a list of five names. She wanted the first one. She typed names[1] and got the second name instead. She tried names[0], which felt wrong to her, and it worked. She wrote, "Why does the first thing live at zero? That can't be right."

It is right. And it is the single most confusing thing about lists for almost everyone I teach. So let's sit with that first, before anything else.

The first item is at position zero

When you write a list in Python, the items are kept in order, and Python numbers those positions starting from zero.

names = ["Kojo", "Ama", "Yaw", "Akua", "Kweku"]
print(names[0])    # Kojo
print(names[1])    # Ama
print(names[4])    # Kweku

There is no deep, mystical reason for this. It is a convention from older programming languages, where the number in the square brackets wasn't really a "position" — it was an offset from the start. The first item is zero steps from the start. The second item is one step from the start. By the time Python came along, this convention was so embedded in how programmers thought that it stuck.

You don't have to like it. You just have to remember it. After about a week of practice, your fingers will start typing [0] for "the first one" without your brain even checking. Until then, expect to bump into it. Everyone does.

The flip side: a list of five items has positions 0 through 4. There is no position 5. If you ask for names[5], Python will tell you the truth — IndexError: list index out of range — rather than making something up.

Slicing — asking for a stretch of seats

Think of a lecture hall with a row of numbered seats. Seat 0, seat 1, seat 2, and so on. If I tell you to grab everyone in seats 1 through 4, what do you do? You probably grab seats 1, 2, 3, and 4. Four people.

Python disagrees. In Python, this is a slice:

names = ["Kojo", "Ama", "Yaw", "Akua", "Kweku"]
print(names[1:4])    # ["Ama", "Yaw", "Akua"]

Three people, not four. The rule is: start is included, end is not. Read names[1:4] as "give me seat 1 up to but not including seat 4."

This trips up everyone. I am calling it out here because if I don't, you will spend an afternoon debugging a program that's off by one item and you will not understand why.

There is a small reward for tolerating this rule. The number of items you get back equals end - start. names[1:4] returns 4 - 1 = 3 items. Once that clicks, slicing stops feeling arbitrary and starts feeling clean.

You can also leave a side blank:

print(names[:3])     # ["Kojo", "Ama", "Yaw"]   — from the start
print(names[2:])     # ["Yaw", "Akua", "Kweku"] — to the end

A blank means "as far as you can go in that direction." I use this constantly.

A list is not a stone tablet

Lists change. That's most of why we use them.

Picture a wedding guest list. You write it on a Monday. By Wednesday your cousin asks to bring her partner. On Friday someone drops out. The list isn't broken — it's doing its job. A real list grows and shrinks during the week.

Python lists are the same. The three moves you'll use most:

guests = ["Esi", "Kwame", "Adwoa"]
 
guests.append("Nana")            # add to the end
guests.insert(0, "Yaa")          # put at position 0, push the rest down
guests.remove("Kwame")           # take out the first match
 
print(guests)
# ["Yaa", "Esi", "Adwoa", "Nana"]

.append() is the workhorse — you'll use it more than the other two combined. Almost every program that collects results starts with an empty list and appends to it as it goes.

.insert() is for when position matters. .remove() takes out the first match by value; if the value isn't there, Python will complain, so check with in first if you're not sure.

Is the name on the list?

Speaking of checking — at the door of that same wedding, the host has one job. Take a name, look at the list, decide yes or no. Python does this with the word in:

guests = ["Yaa", "Esi", "Adwoa", "Nana"]
 
print("Kojo" in guests)        # False
print("Esi" in guests)         # True
 
if "Nana" not in guests:
    guests.append("Nana")

in gives you a True or a False. That's it. It pairs beautifully with if, which is how you'll usually use it: "if this name is already on the list, don't add it again."

This is the same in from for name in guests. Same word, slightly different job. One asks a question. The other walks through every answer.

A first taste of list comprehensions

Once you've written a few programs, you'll meet a piece of Python that looks strange the first time:

names = ["Kojo", "Ama", "Yaw"]
shouted = [name.upper() for name in names]
print(shouted)    # ["KOJO", "AMA", "YAW"]

That's a list comprehension. Read it left to right, in English: "give me the upper-case version of each name in this list, as a new list."

We will spend proper time on these later. For now, just notice the shape — square brackets, an expression, then a for. When you see that pattern, your brain should think, "ah, building a new list from an old one."

You do not need to write comprehensions yet. You only need to recognise one when it walks past.

Take this with you

Lists are the first data structure that really lets you hold something — a queue at a bus station, a row of seats, a guest list at a wedding. The four moves you've just learned (reach in by position, slice, change, ask "is it in there?") will show up in every program you write from now on. None of them are clever. They're vocabulary. Use them enough this week that you stop having to think about them, and the harder things waiting in Week 3 will feel a great deal less hard.