PYTHON / LISTS AND TUPLES
Creating and indexing lists
Build lists with literals or list(), measure them with len(), and read or replace any element using positive and negative indices.
What you will learn
- Create lists with [] literals or by converting an iterable with list()
- Read any element with xs[i], where valid indices run 0 to len(xs) - 1
- Use negative indices to count back from the end without calling len()
- Replace one element in place with xs[i] = value, and predict IndexError
Understanding Creating and indexing lists
A list is an ordered container written with square brackets, as in [18, 21, 19]. The order you type is the order Python keeps, and that order is what gives every element a stable numeric position. Lists do not care what they hold, so [42, "text", 3.5, None] is perfectly legal; the container tracks positions, not types. You can also build one from an existing iterable with the list() function, which walks the iterable and collects its items, so list("hello") produces five one-character strings rather than one five-character string.
Indices count offsets from the start, not item numbers, which is why the first element is xs[0]. The consequence is that a list of length 5 has valid indices 0, 1, 2, 3, 4, and the largest one is len(xs) - 1. Asking for xs[5] raises IndexError rather than returning something empty, because Python refuses to invent a position that was never allocated. This is a deliberate design choice: a silent None would let an off-by-one error travel far from the line that caused it.
Negative indices are shorthand for the same slots seen from the other end: Python converts xs[-k] into xs[len(xs) - k] before looking anything up. That is why xs[-1] is the last element and xs[-len(xs)] is the first, and why xs[-0] is not the last element at all, since -0 is just 0. Because a list is mutable, an index can also appear on the left of an assignment, and xs[2] = 30 overwrites the object stored in that slot while leaving the list's length untouched.
temps = [18, 21, 19, 25, 22]
print(temps)
print(len(temps))
print(temps[0], temps[3])
print(temps[-1], temps[-2])
temps[2] = 30
print(temps)
try:
print(temps[5])
except IndexError as e:
print("IndexError:", e)Every list element is addressed by an integer offset from the start, and negative offsets are simply that same offset measured from the end.
Worked examples
Building lists two ways
Shows a literal holding mixed types, a list built from a string, and the empty list.
letters = list("hello")
print(letters)
print(letters[0], letters[4])
mixed = [42, "forty-two", 3.5, True, None]
print(len(mixed))
print(mixed[1], mixed[-2])
empty = []
print(empty, len(empty))Example explained
Line 1list("hello") iterates the string one character at a time, so each character becomes a separate element.
Line 2letters[4] is the last valid index here because the list has length 5.
Line 3mixed shows that positions are independent of type: an int, a str, a float, a bool and None coexist.
Line 4empty has length 0, so every index is out of range; there is no valid index at all.
Negative indices are offsets from the end
Demonstrates that xs[i] and xs[i - len(xs)] name the same slot.
names = ["ana", "bo", "cy", "dee"]
n = len(names)
for i in range(n):
print(i, i - n, names[i] == names[i - n])
print(names[n - 1], "is the last item")Example explained
Line 1i - n turns each forward index into its negative twin, so 0 pairs with -4 and 3 pairs with -1.
Line 2The comparison is True on every row because both expressions reach the identical slot.
Line 3names[n - 1] is the last element, which is why len(xs) itself is one step too far.
Indices must be integers
Shows computed indices working and a float index failing.
scores = [90, 75, 88]
i = 1
print(scores[i])
print(scores[i + 1])
mid = len(scores) / 2
try:
print(scores[mid])
except TypeError as e:
print("TypeError:", e)
print(scores[len(scores) // 2])Example explained
Line 1An index can be any integer expression, so scores[i + 1] is evaluated to scores[2] before the lookup.
Line 2len(scores) / 2 gives 1.5, and a fractional position has no meaning, so Python raises TypeError instead of rounding.
Line 3Floor division // keeps the result an int, which is the usual fix when computing a middle position.
Important notes
Index assignment only overwrites an existing slot: xs[3] = 9 on a three-element list raises IndexError rather than growing the list.
Naming a variable list shadows the built-in, so a later list("abc") fails with a TypeError about the object not being callable.
Common mistakes
Reading xs[len(xs)] to get the last element; that index is one past the end and raises IndexError immediately.
Treating xs[1] as the first element out of 1-based habit, which silently skips element 0 and shifts every result.
Writing xs[-0] hoping for the last element; -0 equals 0, so you quietly get the first element instead of an error.
Try it yourself
Change, predict, then run
Create a list of the five weekday names, then print its length, the first day, the last day using a negative index, and the middle day using an index computed from len(). Finally overwrite the third element with "WEDNESDAY" and print the whole list.
Open the Python workspaceCheck your understanding
A function receives a non-empty list xs whose length it does not know. Which expression reliably reads the final element?
- Both xs[-1] and xs[len(xs) - 1]
- Only xs[len(xs)]
- Only xs[-0]
- Both xs[len(xs)] and xs[-0], since each means 'the far end'
Show answer
len(xs) - 1 is the largest valid index, and xs[-1] is translated internally to exactly that, so both reach the final element. xs[len(xs)] is tempting because len looks like a count of the last item, but it is one position past the end and raises IndexError; xs[-0] is just xs[0], the first element.