Lesson Review
Lists: Indexing & Slicing
[10, 20, 30]nums[0]nums[-1]nums[1:3]nums[::-1]
Store many values in one variable, then pull out exactly the ones you need.
By the end of this lesson you will be able to:
- Create lists with square brackets, including empty and mixed-type lists.
- Access items with positive indexing (counting from the front, starting at 0).
- Access items with negative indexing (counting from the back, starting at -1).
- Extract sublists with slice notation:
list[start:stop:step].
How to use this page: Use Next and Back (or the arrow keys). Click the boxes in the index ruler, drag through slices in the slice visualizer, and take on the index and slice challenges. Every result comes from running real Python rules.
Words you will hear today
| Word | What it means | Example |
| list | An ordered collection of values in one variable | [10, 20, 30] |
| item / element | One value inside a list | 20 |
| index | An item's position number | nums[0] |
| slice | A new list made from a range of positions | nums[1:3] |
| mutable | Can be changed after it is created | lists are mutable |
| IndexError | The error for an index that does not exist | nums[99] |
- Create, index (positive and negative), slice.
- Item, index, slice, mutable, IndexError.
So far, each variable has held one value. Today we learn about lists, which hold many values in order. Then we learn the two ways to get values back out: indexing, which grabs one item, and slicing, which grabs a group of items.
Creating Lists
What is a list?
A list stores many values, called items (or elements), in order, inside one variable. The Python tutorial describes it like this:
"The most versatile is the list, which can be written as a list of comma-separated values (items) between square brackets."
- Square brackets
[ ] start and end the list.
- Commas separate the items.
- The order you write them in is the order they stay in.
len(list) tells you how many items it has.
Without a list: one variable per score
score1 = 88
score2 = 92
score3 = 75
With a list: one variable holds them all
scores = [88, 92, 75]
print(scores) # [88, 92, 75]
print(len(scores)) # 3
print(type(scores)) # <class 'list'>
Think of a list like a row of numbered lockers. Each locker holds one item, and each has a number (its index) so you can find it.
Why lists make programs better
- They grow. With separate variables, a 31st student means writing
score31 and changing your code. A list can just get one more item.
- They work with loops.
for score in scores: handles 3 scores or 3,000 with the same two lines.
- They work with built-in tools.
sum(scores), max(scores), and min(scores) work on a whole list at once.
scores = [88, 92, 75]
print(sum(scores) / len(scores)) # the average: 85.0
- List = ordered collection of items in [ ], separated by commas.
- len() counts the items.
- Lists grow, work with loops, and work with sum, max, min.
Imagine storing the scores for a class of thirty students with thirty separate variables. A list lets you keep all of them together under one name, in order. The brackets mark the beginning and end, and the commas separate the items.
Creating Lists
Creating lists
| Kind | Example |
| Numbers | scores = [88, 92, 75] |
| Strings | names = ["Ava", "Ben", "Cy"] |
| Empty list | cart = [] (length 0) |
| Mixed types | info = ["Ava", 15, 3.8, True] |
| From a range | list(range(5)) → [0, 1, 2, 3, 4] |
| From a string | list("hey") → ['h', 'e', 'y'] |
| A list inside a list | grid = [[1, 2], [3, 4]] |
Lists can mix types, but the tutorial notes that "usually the items all have the same type." A list of all scores or all names is easier to work with.
Forgot a comma? [1 2 3] gives SyntaxError: invalid syntax. Perhaps you forgot a comma?
Pick a way to create items and run it. Watch the length and the type.
Reading what Python prints
When Python prints a list, it shows strings with quotes: ['h', 'e', 'y']. That is how you can tell the difference between the list [1, 2] (numbers) and ['1', '2'] (strings). The quotes are not part of the values; they just show the type.
Spreading a list over several lines
Long lists can be written over several lines. Python keeps reading until it finds the closing ]:
menu = [
"pizza",
"tacos",
"salad",
]
The comma after the last item is allowed, and it makes it easy to add another item later.
- [], list(range()), list("text"), mixed, nested.
- Missing comma is a SyntaxError.
- Printed lists show quotes around strings so you can see the type.
Here are the common ways to create a list. The empty list is useful when you plan to add items later. Notice that Python prints strings inside a list with quotes around them, so you can tell the string "5" from the number 5. Try the last option to see the missing-comma error.
Indexing
Every item has two index numbers
- An index is an item's position number. You put it in square brackets after the list name:
colors[2].
- Positive indexes count from the front, starting at 0.
- Negative indexes count from the back, starting at -1.
Why does it start at 0? Think of the index as "how many steps from the front." The first item is 0 steps from the front.
Shortcut: for a list of length n, the negative index is the positive index minus n. In a 6-item list, index 2 is the same item as index 2 - 6 = -4.
Click any box to see both of its indexes, or type an index to try.
Why the last index is one less than the length
A list with 6 items has indexes 0, 1, 2, 3, 4, 5. That is still 6 numbers, but because the counting started at 0, it ends at 5. So the last index is always length − 1.
A handy trick: the middle item
len(items) // 2 gives the index of the middle item. For a 5-item list, 5 // 2 is 2, the third item, which has 2 items on each side. Use //, not /, because an index must be a whole number.
- Positive: 0 to len - 1. Negative: -1 to -len.
- negative = positive - len.
- Last index = len - 1. Middle index = len // 2.
Every item in a list has two addresses. From the front, the first item is zero. From the back, the last item is negative one. Click a few boxes and look at both numbers. Then type an index that is too big, like 6, and see what Python says.
Indexing
Positive indexing: counting from the front
scores = [88, 92, 75, 64, 99]
# index: 0 1 2 3 4
print(scores[0]) # 88 (first item)
print(scores[2]) # 75 (third item)
print(scores[4]) # 99 (last item)
print(scores[5]) # IndexError!
- The first item is
[0], so the second item is [1]. The item number is always one more than its index.
- The last positive index is
len(scores) - 1. Here that is 4.
- An index past the end gives
IndexError: list index out of range.
- The index must be an int.
scores[1.0] gives TypeError: list indices must be integers or slices, not float.
Change the index and run it. What is the biggest index that works?
Using a variable as the index
The number inside the brackets can be any expression that gives an int, including a variable or math:
pick = 2
print(scores[pick]) # 75
print(scores[pick + 1]) # 64
print(scores[len(scores) - 1]) # 99, the last item
This is what makes loops with indexes possible: for i in range(len(scores)): then scores[i] visits every position.
- First is [0]. Last is [len - 1].
- Too big: IndexError. Not an int: TypeError.
- The index can be a variable or an expression.
The most common list bug is forgetting that counting starts at zero. If you want the third score, you ask for index 2. And because the first index is zero, the last index is one less than the length. Five items means the last index is four.
Indexing
Negative indexing: counting from the back
scores = [88, 92, 75, 64, 99]
# index: -5 -4 -3 -2 -1
print(scores[-1]) # 99 (last item)
print(scores[-2]) # 64 (second to last)
print(scores[-5]) # 88 (first item)
print(scores[-6]) # IndexError!
The tutorial explains why it starts at -1: "Note that since -0 is the same as 0, negative indices start from -1."
Why it is useful: scores[-1] always gets the last item, no matter how long the list is. You do not need to know the length.
| To get the... | Positive | Negative |
| first item | scores[0] | scores[-5] |
| last item | scores[4] or scores[len(scores) - 1] | scores[-1] |
| second to last | scores[3] | scores[-2] |
How Python converts a negative index
Python adds the length: for a 5-item list, scores[-2] becomes scores[5 + (-2)], which is scores[3]. That is why the most negative index that works is -len: it turns into 0.
When to use which: use positive indexes when you count from the start ("the first three players"). Use negative indexes when you care about the end ("the most recent score," "the last item added").
- -1 is last, -2 second to last, -len is first.
- Too far back: IndexError.
- Negative index i becomes len + i.
Negative indexes count backward from the end. Negative one is always the last item, which is very handy because lists often change size. Negative two is the second to last, and so on, until you reach the first item at negative five.
Practice
Index challenge
Find the item using both a positive and a negative index, then click Check. Practice only; nothing is recorded.
Strategy
- Write the positive indexes above the items, starting at 0.
- Write the negative indexes below, starting at −1 on the right.
- Check: for any item, positive − negative should equal the length.
- Count from 0 at the front, from -1 at the back.
Use your fingers if it helps. Point to the first planet and say zero, then count forward. For the negative index, point to the last planet and say negative one, then count backward. Check your answer, then click New item and try again.
Indexing
Using an index to change an item
Lists are mutable: you can change their items. Put the index on the left side of =:
colors = ["red", "green", "blue"]
colors[0] = "orange" # replace the first item
colors[-1] = "purple" # replace the last item
print(colors) # ['orange', 'green', 'purple']
- Shortcut operators work too:
scores[1] += 5.
- You can only replace an item that exists.
colors[5] = "pink" gives IndexError: list assignment index out of range.
Strings can be indexed but not changed. The tutorial: "Unlike strings, which are immutable, lists are a mutable type." So word[0] = "b" gives TypeError: 'str' object does not support item assignment.
Step through and watch colors change in the Variables panel. The last line shows what happens with a string.
Swapping two items
Python can swap two items in one line. The right side is worked out first, then both values are stored:
colors = ["red", "green", "blue"]
colors[0], colors[2] = colors[2], colors[0]
print(colors) # ['blue', 'green', 'red']
Adding a new item? You cannot assign to an index that does not exist yet. To add to the end, use colors.append("pink"), which you will learn with list methods.
Why can lists change but strings cannot? Lists are made to be updated as a program runs (a shopping cart, a high-score table). Strings are treated as single values, like numbers, so they stay fixed.
- list[i] = value replaces an item.
- Strings are immutable: no item assignment.
- a[i], a[j] = a[j], a[i] swaps two items.
Indexing does not only read items. It can also replace them. Put the index on the left of the equals sign, and the new value on the right. Lists let you do this because they are mutable, which means changeable. Strings are not, so the last line in the editor causes an error.
Slicing
Slicing: list[start:stop]
A slice gets a sublist: a new list holding a range of items.
start is the index of the first item you want.
stop is the index where Python stops. That item is not included.
- The number of items is
stop - start.
The tutorial: "Note how the start is always included, and the end always excluded."
letters = ["a", "b", "c", "d", "e", "f"]
print(letters[1:4]) # ['b', 'c', 'd'] (4 - 1 = 3 items)
print(letters[0:2]) # ['a', 'b']
print(letters[3:4]) # ['d'] (a list with one item)
Pick a start and a stop. The highlighted boxes are the slice. Leave step blank for now.
Reading a slice step by step
For letters[1:4]:
- Start at index 1:
'b'. Include it.
- Index 2:
'c'. Include it.
- Index 3:
'd'. Include it.
- Index 4 is the stop. Stop before it.
Why leave out the stop? It makes the math easy: the number of items is just stop − start, and two slices that share a number fit together perfectly with nothing missing and nothing doubled: letters[0:3] + letters[3:6] is the whole list.
The slice is a new list. Changing it does not change the original list.
- Start included, stop excluded.
- Length = stop - start.
- Slices that share a boundary fit together perfectly.
A slice uses a colon inside the brackets. The rule to remember is: start is in, stop is out. So letters one to four gives you indexes one, two, and three. A good way to think about it is that the slice stops right before the stop index.
Slicing
Leaving out start or stop
The tutorial: "an omitted first index defaults to zero, an omitted second index defaults to the size of the string being sliced." Lists work the same way.
| Slice | Means | With letters (a to f) |
letters[:3] | from the start up to index 3 | ['a', 'b', 'c'] (first 3) |
letters[3:] | from index 3 to the end | ['d', 'e', 'f'] |
letters[:] | everything | a copy of the whole list |
Neat fact: letters[:i] + letters[i:] always gives back the whole list, because the item at i goes in exactly one of the two halves.
A slice is a new list. The tutorial: "All slice operations return a new list containing the requested elements." So a[:] makes a copy, but b = a just gives the same list a second name.
What the demo shows
After a[0] = 99, both a and b show 99, because b = a did not make a copy. It made a second name for the same list, like two nicknames for one person. Only c, made with a[:], is a real copy and keeps the 1.
Remember: = with a list never copies it. If you want an independent list to change safely, use a[:] or a.copy().
- [:n] first n items. [n:] from n to the end. [:] a copy.
- b = a is the same list; a[:] is a new list.
- b = a shares the list; a[:] copies it.
You can leave out either number. No start means from the beginning, and no stop means all the way to the end. Leave out both and you get a copy of the whole list. Step through the editor: when we change a, the name b changes too, because b is the same list. But c, the copy, does not.
Slicing
Negative indexes in slices
Negative indexes work in slices too. They are great for "the last few" or "all but the last."
| Slice | Result with letters | In words |
letters[-3:] | ['d', 'e', 'f'] | the last 3 items |
letters[:-1] | ['a', 'b', 'c', 'd', 'e'] | all but the last item |
letters[1:-1] | ['b', 'c', 'd', 'e'] | all but the first and last |
letters[-4:-2] | ['c', 'd'] | start included, stop excluded |
The same rule applies: start is in, stop is out. letters[-4:-2] starts at -4 ('c') and stops before -2 ('e').
Try negative starts and stops in the visualizer. Can you make ['e', 'f'] two different ways?
Two ways to say "the last 3"
For a 6-item list, letters[3:] and letters[-3:] give the same result. But only [-3:] keeps working if the list gets longer or shorter. Negative slices are the reliable way to get items from the end.
Answering the challenge
['e', 'f'] can be made with letters[4:], letters[-2:], letters[4:6], or even letters[-2:6]. You can mix positive and negative numbers in the same slice.
- [-n:] last n items. [:-1] all but last. [1:-1] all but first and last.
- [-n:] keeps working when the list length changes.
Negative slices are some of the most useful slices in Python. Negative three colon means the last three items, no matter how long the list is. Colon negative one means everything except the last item. Try to build the same slice two different ways, once with positive numbers and once with negative ones.
Slicing
The step: list[start:stop:step]
A third number, the step, says how far to jump each time. The default step is 1.
| Slice | Result with letters | In words |
letters[::2] | ['a', 'c', 'e'] | every other item |
letters[1::2] | ['b', 'd', 'f'] | every other, starting at index 1 |
letters[::3] | ['a', 'd'] | every third item |
letters[::-1] | ['f', 'e', 'd', 'c', 'b', 'a'] | the whole list, reversed |
letters[4:1:-1] | ['e', 'd', 'c'] | backward from 4, stop before 1 |
A negative step goes backward, so start should be after stop. A step of 0 is an error: ValueError: slice step cannot be zero.
Now use the step box. Try step = -1 with start and stop left blank.
How a step slice picks items
For letters[1::2]: start at index 1 ('b'), then keep adding 2: index 3 ('d'), index 5 ('f'), index 7 is past the end, so stop.
Backward slices: with letters[4:1:-1], Python starts at 4 and subtracts 1 each time: 4, 3, 2. It stops before reaching 1. If the start were smaller than the stop with a negative step, the result would be empty, because you can never walk backward from a small number to a bigger one.
[::-1] makes a reversed copy. The original list stays in its order.
- Step = jump size. [::2] every other. [::-1] reversed.
- Step 0 is a ValueError.
- A negative step walks backward; start must be bigger than stop.
The step is the jump size. A step of two skips every other item. A negative step walks backward, so colon colon negative one is a famous Python trick for reversing a list. With a negative step, the start needs to be to the right of the stop, because you are walking left.
Slicing
Index versus slice
| Index nums[2] | Slice nums[2:3] |
| Gives you | one item | a new list |
| Result | 30 | [30] |
| Out of range | IndexError | No error. You get what exists (maybe []) |
The tutorial: "Attempting to use an index that is too large will result in an error" but "out of range slice indexes are handled gracefully when used for slicing."
Strings slice the same way. "Python"[0] is 'P', "Python"[-1] is 'n', and "Python"[1:4] is 'yth'.
Run each option and compare the type of the result.
Why the type of the result matters
An index and a slice can look similar but give different things, and that changes what you can do next:
nums[2] + 5 works: 30 + 5 is 35.
nums[2:3] + 5 fails with a TypeError: you cannot add a number to a list.
Rule: no colon means one item. A colon means a list, even if that list holds only one item or none.
- Index: one item, can IndexError.
- Slice: a list, never IndexError.
- No colon: one item. A colon: always a list.
Indexes and slices look alike, but they give back different things. An index gives the item itself. A slice always gives a list, even if it only has one item, or none at all. And slices are forgiving: if you go past the end, Python just gives you whatever items exist.
Practice
Slice challenge
Type a slice in brackets (like [0:2]) that produces each target list, then click Check. There is often more than one right answer. Practice only; nothing is recorded.
Strategy
- Find the index of the first item you want: that is your start.
- Find the index of the last item you want and add 1: that is your stop (with a step of 1).
- Need every other item, or a reversed list? Add a step.
- Check your answer by counting: does stop − start (with step 1) equal the number of items in the target?
- Many answers can be correct: [0:3] and [:3] are the same slice.
For each target, find where it starts and where it ends. Remember that the stop index is one past the last item you want. For the reversed ones, use a negative step. If you get one right, try to find a second slice that also works.
Debugging
Common mistakes
| Mistake | Code | What happens (Python 3.13) | Fix |
| Thinking the first index is 1 | names[1] for the first name | No error. You get the second name | names[0] |
| Using the length as an index | names[len(names)] | IndexError: list index out of range | names[-1] |
| Expecting the stop item | nums[1:3] for 3 items | No error. Only 2 items | nums[1:4] |
| Expecting an item from a slice | nums[0:1] | No error. You get [10], a list | nums[0] |
| Forgot a comma | [1 2 3] | SyntaxError: invalid syntax. Perhaps you forgot a comma? | [1, 2, 3] |
| Float index | nums[len(nums) / 2] | TypeError: list indices must be integers or slices, not float | nums[len(nums) // 2] |
| Parentheses instead of brackets | nums(0) | TypeError: 'list' object is not callable | nums[0] |
| Indexing an empty list | [][0] | IndexError: list index out of range | Check len() first |
| Changing a character in a string | word[0] = "J" | TypeError: 'str' object does not support item assignment | Build a new string |
| Step of zero | nums[::0] | ValueError: slice step cannot be zero | Use 1, 2, -1... |
Try some at the prompt. Start by typing the first line:
nums = [10, 20, 30, 40]
nums[4]
nums[-1]
nums[1:3]
nums[0:1]
nums[2:99]
nums[len(nums) / 2]
nums(0)
Off-by-one: the most common list bug
Many of these mistakes come from counting from 1 instead of 0, or expecting the stop to be included. They do not cause errors, so the program runs and shows the wrong item. Whenever a result is "one off," check:
- Did I use the item number (1st, 2nd) instead of the index (0, 1)?
- Did I expect the slice's stop item to be included?
Error words to recognize: "index out of range" means too big or too negative. "must be integers" means you used a float. "not callable" means you used ( ) where you needed [ ].
- Off-by-one mistakes give no error: they give the wrong item.
- "One off" results: check item number vs. index, and stop exclusion.
The first four mistakes are called off-by-one errors, and they are the sneakiest because Python does not complain. You just get the wrong item. Whenever you index or slice, ask yourself: is my first index zero, and is my stop one past the last item I want?
Practice
Check yourself
Every question uses this list: nums = [4, 8, 15, 16, 23, 42]. Choose an answer, then click Check. Practice only; nothing is graded or recorded.
Write this down first:
4 8 15 16 23 42
0 1 2 3 4 5
-6 -5 -4 -3 -2 -1
Then: is the question an
index (one value) or a
slice (a list)?
- Write the indexes above and below the list on scratch paper.
Before you answer, write the list on scratch paper with the positive indexes above it and the negative indexes below it. Then each question is just reading off your chart.
Summary
Cheat sheet
Create
nums = [10, 20, 30, 40]
empty = []
mixed = ["Ava", 15, True]
digits = list(range(5))
len(nums) # 4
Index (one item)
nums[0] # 10 first
nums[2] # 30
nums[-1] # 40 last
nums[-2] # 30
nums[1] = 99 # change it
Positive: 0 to len - 1.
Negative: -1 to -len.
Slice (a new list)
nums[1:3] # [20, 30]
nums[:2] # [10, 20]
nums[2:] # [30, 40]
nums[-2:] # [30, 40]
nums[::2] # [10, 30]
nums[::-1] # reversed
nums[:] # a copy
Remember:
- Indexes start at 0 from the front and -1 from the back
- Slices: start is in, stop is out; a missing start or stop means "from the beginning" or "to the end"
- An index too big is an
IndexError; a slice too big is fine
- An index gives an item; a slice gives a list
Key words to know
list · item / element · index (positive and negative) · len() · mutable · slice · start / stop / step · sublist · copy ([:]) · IndexError · off-by-one
- Create with [ ]. Index for one item. Slice for a sublist.
- Know the key words: index, slice, mutable, copy, off-by-one.
Let's wrap up. Square brackets create a list. Square brackets with one number get one item: zero from the front or negative one from the back. Square brackets with a colon get a slice, a new list, where start is in and stop is out. Practice these until counting from zero feels natural.