BeginnerPython · 46 min · 20 challenges

Lists: Beginner

On Spotify, the Queue screen behind the Now Playing view is an ordered list of track IDs. Tapping Add to Queue appends a track at the end, a finished song comes off the front, and dragging a track out of the queue deletes it by position. Each move is a single list operation, and an off-by-one index plays the wrong song to millions of listeners.

Creating Lists

Daily Life
Interviews
A list holds any number of values in one variable, in the order you wrote them. Where a plain variable stores 1 score, a list stores 3 scores or 300 of them and keeps track of which came first. Python writes a list as square brackets around values separated by commas.
scores = [88, 92, 75]
days = ["Mon", "Tue", "Wed", "Thu"]
prices = [4.99, 12.50, 0.75]
empty = []

The brackets mark where the list starts and ends, and each comma separates 1 item from the next. scores holds 3 integers, days holds 4 strings, prices holds 3 floats, and empty holds nothing at all, which still counts as a list.

scores = [88, 92, 75]
print(scores)
print(len(scores))
print(type(scores) == list)
>>>Output
[88, 92, 75]
3
True

print shows the whole list in its bracket form, so every item is visible at once. len counts the items and returns 3 for scores. type(scores) == list is True, the same way type(88) == int and type("Mon") == str are both True. A list is a type of its own, and the values inside it keep their own types.

Items keep their order

A list remembers the sequence you gave it. A for loop visits each item from first to last, the same way it walks through range, except the loop variable receives the item itself rather than a count. The list below prints its 3 cities in the order they were written, every time the program runs.
cities = ["Lagos", "Oslo", "Lima"]
for city in cities:
print(city)
visited = []
print(visited)
print(len(visited))
>>>Output
Lagos
Oslo
Lima
[]
0
The empty list prints as [] and has length 0. A loop over an empty list runs its body 0 times and moves on. Many programs begin with an empty list and add items as they go, which Section 2 covers in detail.

Any type can go inside

A list takes values of any type, and a single list can mix them. A row from a spreadsheet fits in 1 list: the passenger's name and seat number sit beside the bag weight and a flag for whether the ticket is paid.
ticket = ["Ada", 34, 12.5, True]
print(ticket)
print(len(ticket))
print(type(ticket) == list)
>>>Output
['Ada', 34, 12.5, True]
4
True

Python prints strings inside a list with single quotes, even when you typed double quotes. Both spellings make the same string, and the quotes are part of the display rather than part of the value.

ListItemslen()
[88, 92, 75]3 integers3
["Mon", "Tue"]2 strings2
["Ada", 34, True]a string, an int and a bool3
[4.99]1 float1
[]nothing0
A list of 1 item is still a list, written with brackets around the lone value. The brackets, not the number of items, make something a list.
votes = ["yes", "no", "yes", "yes"]
print(votes)
print(len(votes))
>>>Output
['yes', 'no', 'yes', 'yes']
4
A list keeps repeated values too. Each "yes" is its own item in its own place, so 4 votes give a length of 4 even though only 2 different words appear. Section 3 depends on this, when a grocery list holds the same item twice.
TIP
Name a list for what it holds, in the plural: scores, cities, prices. A loop variable can then take the singular for city in cities so the code reads as a sentence.
One list, many values
  • ▸Square brackets open and close a list; commas separate the items.
  • ▸Items keep the order they were written in, and a for loop visits them in that order.
  • ▸Any type can go inside, and types can mix within 1 list.
  • ▸len(items) returns the count, which is 0 for [].

len gives you a number

len returns an ordinary integer, so you can store it, do arithmetic with it or test it in an if like any other number. A small hotel keeps its booked guests and its waiting list as 2 separate lists. The front desk wants the total of both and the gap between them, and it also wants to know how many of its 5 rooms are still free.

booked = ["Ada", "Lin", "Omar"]
waitlist = ["Zoe"]
print(len(booked) + len(waitlist))
print(len(booked) - len(waitlist))
free = 5 - len(booked)
print("rooms free:", free)
>>>Output
4
2
rooms free: 2

An empty list adds 0 to any total, so a sum of lengths counts exactly the items present. If the waiting list were [], the first line would print 3 and the second would print 3 as well.

Multiple Choice

What does this program print?

items = ["a", "b"]
more = []
print(len(items) + len(more))

Accessing Items by Index

Daily Life
Interviews
Every item in a list sits at a numbered position called its index, and the numbering starts at 0. The first item is at index 0, the second at index 1, and the last item of a list with 4 items is at index 3. To read an item, write the list's name followed by the index in square brackets.
ItemMonTueWedThu
Index0123
Negative index-4-3-2-1
days = ["Mon", "Tue", "Wed", "Thu"]
print(days[0])
print(days[3])
first = days[0]
print(first + " starts the week")
>>>Output
Mon
Thu
Mon starts the week

days[0] is an expression with a value, so it goes anywhere a value goes: inside print, on the right of an assignment, or joined to another string with +. The index can be a variable too: with i = 2, days[i] is "Wed".

Counting from the end

A negative index counts backwards from the end. Index -1 is the last item, -2 the one before it, and -4 is the first item of a 4-item list. This saves working out the length first: the last item is at -1 whether the list has 4 items or 4000.
days = ["Mon", "Tue", "Wed", "Thu"]
print(days[-1])
print(days[-4])
print(days[len(days) - 1])
>>>Output
Thu
Mon
Thu
The third line shows the long way to reach the last item: the length is 4, the last index is 3, so len(days) - 1 lands on "Thu". Negative indexing does the same arithmetic for you.

Past the end

An index that names no item stops the program. A list of 3 items has indexes 0 through 2, so asking for index 3 raises an IndexError, and nothing after that line runs. The first print below succeeds and the second one ends the program.
colors = ["red", "green", "blue"]
print(colors[2])
print(colors[3])
print("done")

Run, this program prints blue and then stops with a traceback whose last line is IndexError: list index out of range. The word done never prints. That message always means the number in the brackets was too large for the list, or too negative: index -4 on this 3-item list fails the same way. Section 4 shows how to check before asking.

Replacing an item

The same bracket form on the left of = writes a value into a position. The old item goes away and the new one takes its index; the list keeps its length. Press play and watch index 2 change.
1planets = ["Mercury", "Venus", "Mars", "Jupiter"]
2print("Before:", planets)
3
4# Fix: Earth should be at index 2
5planets[2] = "Earth"
6print("After:", planets)
7
8# Update the first planet
9planets[0] = "MERCURY"
10print("Final:", planets)
planets
[
]
0123
output0 lines
planets is a new list that holds 'Mercury', 'Venus', 'Mars', 'Jupiter'
1/7
Index 2 held "Mars" and after the assignment it holds "Earth". The other 3 items stay where they were, and the final line shows index 0 replaced as well. Assigning to an index that doesn't exist, such as planets[4] on this 4-item list, raises the same IndexError as reading one.

Index plus loop

range(len(tasks)) produces 0, 1, 2 for a list of 3 items, exactly the valid indexes. Looping over that range gives you both the position and, through tasks[i], the item at it.
tasks = ["write", "test", "ship"]
for i in range(len(tasks)):
print(i, tasks[i])
>>>Output
0 write
1 test
2 ship
TIP
Reach for range(len(...)) only when the position matters. When you only need the items, for task in tasks is the clearer loop, and it can't go out of range.
Multiple Choice

What does this program print?

letters = ["p", "q", "r", "s"]
letters[1] = letters[-1]
print(letters)

The third item is at index 2

People count positions from 1 and Python counts indexes from 0, so the item a person calls third sits at index 2: subtract 1 from the position. Counting from the back needs no subtraction, because the last item is -1, the second to last is -2, and so on.
finishers = ["Kim", "Raj", "Tess", "Ola", "Bea"]
print(finishers[2])
print(finishers[-2])
print(finishers[4] == finishers[-1])
>>>Output
Tess
Ola
True
On a list of 5 items, index -1 names the same item as index 4, and -3 matches 2: a negative index is the positive one minus the length. Forgetting the minus 1 reads the item next door to the one you wanted, and Python raises no error to warn you, because that neighbour sits at a valid index.

Adding Items to Lists

Daily Life
Interviews
Lists change size while a program runs. The append method adds 1 item to the end, in place: the list itself grows and no new list comes back. Call it with a dot after the list's name and the new item in parentheses.
1shopping = ["milk", "bread"]
2print("Before:", shopping)
3
4shopping.append("eggs")
5print("After first append:", shopping)
6
7shopping.append("cheese")
8print("After second append:", shopping)
9
10shopping.append("butter")
11print("Final:", shopping)
shopping
[
]
01234
output0 lines
shopping is a new list that holds 'milk', 'bread'
1/9
After 3 calls, shopping holds 5 items, each new one landing after the previous. The order of the calls becomes the order in the list, so append is the right tool whenever items arrive one at a time and arrival order is the order you want to keep.

Building a list in a loop

The most common way to fill a list: start with [], loop, and append each value as you compute it. The loop below squares the numbers 1 through 5 and the list grows by 1 on every pass. Nothing prints until the loop ends, because the point is to keep the results rather than show them.
1squares = []
2
3for num in range(1, 6):
4 squared = num * num
5 squares.append(squared)
6
7print(squares)
num=
squared=
squares
[
]
01234
output0 lines
squares is a new list that starts empty
1/19
The same shape with an if inside keeps only the values that pass a test. The loop below walks the numbers 1 to 10 and appends the ones divisible by 2, so the result has 5 items while the source still has 10. The source list is never changed; a new list collects the matches.
1source = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
2evens = []
3
4for number in source:
5 if number % 2 == 0:
6 evens.append(number)
7
8print(evens)
number=
source
[
]
0123456789
evens
[
]
01234
output0 lines
source is a new list that holds 1, 2, 3, 4, 5, 6 and 4 more
1/30
The accumulator pattern
  • ▸Create an empty list before the loop starts.
  • ▸Compute 1 value on each pass through the loop.
  • ▸Append it, optionally only when an if test passes.
  • ▸Use the finished list after the loop ends.
The accumulator works with a while loop as well, when the number of passes is not known in advance. The loop below starts at 3, doubles the value on each pass, and appends every value below 100. The list ends with 6 items because the seventh value, 192, fails the test.
values = []
n = 3
while n < 100:
values.append(n)
n = n * 2
print(values)
print(len(values))
>>>Output
[3, 6, 12, 24, 48, 96]
6

Inserting at a position

insert takes 2 arguments: the index where the new item should sit, and the item itself. Everything from that index onward shifts 1 place to the right to make room, so the item that used to be at index 1 answers to index 2 after the insert.
1letters = ["a", "c", "d"]
2print("Before:", letters)
3
4# Insert "b" at index 1
5letters.insert(1, "b")
6print("After insert at 1:", letters)
letters
[
]
0123
output0 lines
letters is a new list that holds 'a', 'c', 'd'
1/5
Index 0 puts the item at the front, which is how an urgent request jumps a queue: the 3 existing items each move 1 place right and the new one takes index 0.
1queue = ["second", "third", "fourth"]
2print("Before:", queue)
3
4# Insert at the beginning
5queue.insert(0, "first")
6print("After:", queue)
queuequeue
front
[
]
back
output0 lines
queue is a new queue that holds 'second', 'third', 'fourth'
1/5
nums = [1, 2, 3]
nums.insert(10, 99)
print(nums)
nums.insert(0, 0)
print(nums)
print(len(nums))
>>>Output
[1, 2, 3, 99]
[0, 1, 2, 3, 99]
5
An insert index beyond the end raises no error: Python places the item at the end, as append would. Watch for this, because a typo in the index produces a list that looks almost right. The second insert at 0 pushes all 4 items along and the length reaches 5.
✓append(item)
  • Adds at the end
  • Takes 1 argument
  • Never shifts other items
  • Use when arrival order is the order you want
✗insert(index, item)
  • Adds at a chosen index
  • Takes 2 arguments, index first
  • Shifts later items right by 1
  • Use when the position must be chosen
TIP
Both methods change the list they are called on and return nothing useful. Write items.append(x) on its own line rather than items = items.append(x), which throws the list away.

Index first, item second

insert reads its first argument as the position and its second as the item, always in that order. On a list of numbers, swapping them raises no error: both arguments are integers, so Python accepts either order and quietly does something else.

a = [1, 2, 3]
a.insert(0, 5)
print(a)
b = [1, 2, 3]
b.insert(5, 0)
print(b)
>>>Output
[5, 1, 2, 3]
[1, 2, 3, 0]

The first call puts 5 at the front. The second puts 0 at the end, because index 5 lies beyond the end of a 3-item list. With a string item the swap fails loudly instead: an index must be a whole number, so names.insert("Zed", 0) raises a TypeError before the list changes.

Removing Items from Lists

Daily Life
Interviews
Each way of taking items out of a list differs in what you give it and what you get back. remove takes a value and returns nothing. pop and del both take an index, but only pop hands the removed item back. Pick by what you know: the value, or the position.

remove: by value

1groceries = ["milk", "eggs", "bread", "eggs", "cheese"]
2print("Before:", groceries)
3
4groceries.remove("eggs")
5print("After removing eggs:", groceries)
groceries
[
]
01234
output0 lines
groceries is a new list that holds 'milk', 'eggs', 'bread', 'eggs', 'cheese'
1/5
groceries held "eggs" twice, and only the first one went. remove searches from the front, deletes the first match and stops. A second remove("eggs") would delete the other one, and a third would fail, because the value is no longer there.
cart = ["pen", "ink"]
cart.remove("pen")
print(cart)
cart.remove("pen")
print(cart)

When run, the program prints ['ink']. Then it stops with a traceback ending in ValueError: list.remove(x): x not in list. The first remove succeeded, so the second found no "pen" left and the last print never ran. The error names the problem as a value problem, which separates it from the index problems below.

pop: by index, with a return

1stack = ["first", "second", "third", "fourth"]
2print("Before:", stack)
3
4# Pop without argument removes last item
5last = stack.pop()
6print("Popped:", last)
7print("After pop():", stack)
8
9# Pop with index removes specific item
10middle = stack.pop(1)
11print("Popped index 1:", middle)
12print("Final:", stack)
last=
middle=
stackstack
top
output0 lines
stack is a new stack that holds 'first', 'second', 'third', 'fourth'
1/9

pop() with no argument removes the last item and returns it. pop(1) does the same for index 1. Either way the removed item comes back, so you can store it in a variable or print it, as the animation does with last and middle.

todo = ["a", "b", "c"]
while len(todo) > 0:
item = todo.pop()
print("doing", item, "left", len(todo))
print(todo)
>>>Output
doing c left 2
doing b left 1
doing a left 0
[]
The loop stops when len(todo) hits 0. Calling pop on an empty list raises IndexError: pop from empty list, which the while condition prevents here by checking the length before each pop.

On a list of numbers, remove and pop look alike, since each takes a number in parentheses. remove reads the number as a value to find, and pop reads it as a position.

a = [5, 2, 9, 2]
a.remove(2)
print(a)
b = [5, 2, 9, 2]
b.pop(2)
print(b)
>>>Output
[5, 9, 2]
[5, 2, 2]

a.remove(2) deleted the first 2 it found, which sat at index 1. b.pop(2) went to index 2 and took 9, leaving both 2s in place. The method in front of the parentheses decides what the number means.

del: by index, no return

1numbers = [10, 20, 30, 40, 50]
2print("Before:", numbers)
3
4del numbers[2]
5print("After del [2]:", numbers)
6
7del numbers[0]
8print("After del [0]:", numbers)
numbers
[
]
01234
output0 lines
numbers is a new list that holds 10, 20, 30, 40, 50
1/7
del numbers[2] deletes the item at index 2. del is a statement rather than a method, so there is no dot and no parentheses after it. After the first del, 40 moves from index 3 to index 2, so a second del numbers[2] would remove 40, not 50.
ToolYou giveYou get backFails when
groceries.remove("eggs")a valuenothingthe value is absent (ValueError)
stack.pop()an optional indexthe removed itemthe list is empty or the index is out of range, too large or too negative (IndexError)
del numbers[2]an indexnothingthe index is out of range, too large or too negative (IndexError)
TIP
Indexes shift after any removal. Removing index 1 from a 4-item list leaves indexes 0 through 2, and the item that sat at 2 now answers to 1. Re-read positions after each removal rather than trusting the old numbers.
Multiple Choice

What does this program print?

nums = [5, 6, 7, 8]
nums.pop(0)
del nums[-1]
nums.remove(6)
print(nums)

Choosing among remove, pop and del

Pick the tool by what you have in hand. If you know the value, such as a guest's name, use remove. pop fits when you know the position and need the item afterwards, and it also takes negative indexes. When you know the position and have no further use for the item, del is enough.

waitlist = ["Ann", "Ben", "Cal", "Dee"]
left = waitlist.pop()
print("left:", left)
waitlist.remove("Ann")
del waitlist[0]
print(waitlist)
>>>Output
left: Dee
['Cal']
Dee left the line from the back and her name was kept for the message, so pop fits. Ann cancelled by name, so remove fits. The front slot was dropped with no record, so del fits. Positions close up after each removal: Ben moved from index 1 to index 0 before del ran, so del took Ben and left Cal.

Safe List Access

Daily Life
Interviews
Reading an index that doesn't exist, popping from an empty list and removing a value that is not there all stop a program. Each has a check that costs 1 line and runs before the risky call, so the program prints a message and carries on instead of ending with a traceback.

Check the length before indexing

A list of n items accepts indexes 0 through n - 1. An index is safe when it's below len(items), and, for counting from the front, at least 0. Compare first, read second.
readings = [20.5, 21.0]
wanted = 5
if wanted < len(readings):
print(readings[wanted])
else:
print("no reading at", wanted)
print("still running")
>>>Output
no reading at 5
still running
The else branch prints a message and the program reaches the last line. Without the if, readings[5] would have raised IndexError and "still running" would never appear. The same guard works inside a loop, where and joins the 2 halves of the range check.
temps = [18, 21, 19]
for wanted in [0, 2, 3, -1]:
if wanted >= 0 and wanted < len(temps):
print(wanted, "->", temps[wanted])
else:
print(wanted, "-> skipped")
>>>Output
0 -> 18
2 -> 19
3 -> skipped
-1 -> skipped
This guard treats negative numbers as invalid on purpose. Python would accept temps[-1], but a position that arrives from a form or a counter means a count from the front, and a -1 there is a mistake worth catching rather than a request for the last item.

Check for empty before pop

A loop that pops on every pass needs the list to still have items. Testing len(inbox) > 0 before each pop turns the third and fourth passes into a message instead of a crash.

inbox = ["bill", "invite"]
for step in range(4):
if len(inbox) > 0:
print("read:", inbox.pop())
else:
print("inbox empty")
>>>Output
read: invite
read: bill
inbox empty
inbox empty
The 2 pops empty the inbox, and from then on the else branch answers. All 4 passes complete, and the program never reaches a pop on an empty list.

Count before remove

The count method returns how many times a value appears in the list. A count of 0 means remove would raise ValueError, so test the count first and only remove when it is above 0.
guests = ["Ana", "Bo", "Ana"]
print(guests.count("Ana"))
print(guests.count("Cy"))
if guests.count("Cy") > 0:
guests.remove("Cy")
else:
print("Cy was never on the list")
print(guests)
>>>Output
2
0
Cy was never on the list
['Ana', 'Bo', 'Ana']

Guarding the first and last item

items[0] and items[-1] name positions every non-empty list has, but an empty list has neither a first nor a last item, so both raise IndexError on []. The guard is the same emptiness test used before pop: check the length, then read.

latest = []
if len(latest) > 0:
print("newest:", latest[-1])
else:
print("no readings yet")
latest.append(22.4)
if len(latest) > 0:
print("newest:", latest[-1])
>>>Output
no readings yet
newest: 22.4

Wrap it in a function

A function that returns a fallback value when the index is out of range lets the rest of a program read positions without an if at every call site. The caller always gets a value back: the item when it exists, the fallback when it does not.
1def safe_get(items, index, fallback):
2 if index >= 0 and index < len(items):
3 return items[index]
4 return fallback
5
6queue = ["intro", "verse", "chorus"]
7print(safe_get(queue, 1, "silence"))
8print(safe_get(queue, 3, "silence"))
9print(safe_get(queue, -1, "silence"))
queueitemsqueue
front
[
]
back
output0 lines
defines safe_get
1/15

The function returns the item when the index is in range and the fallback otherwise.

Index 1 passes both halves of the test and "verse" comes back. Index 3 fails the length check and index -1 fails the sign check, so each returns "silence" and the program prints 3 lines with no traceback.
The caller picks the fallback, so each call site decides what missing looks like: 0 for a count, an empty string for a label, "silence" for an empty slot in a playlist. The function only decides whether the index is in range, which keeps it usable for a list of any type.
✓Do
  • Compare the index against len() before reading from the list
  • Test len(items) > 0 before every pop()
  • Test count(value) > 0 before remove(value)
  • Return a fallback from a helper so callers always get a value
✗Don't
  • Assume a list still has items inside a loop that pops
  • Hard-code an index like 3 without checking the length
  • Call remove on a value you never confirmed is present
  • Let a number from outside the program reach [] unchecked
Multiple Choice

What does this program print?

parts = ["cpu", "ram"]
if len(parts) > 2:
print(parts[2])
else:
print(parts[-1])
❯❯❯PUTTING IT ALL TOGETHER

> A pizza shop runs its kitchen from a list of order numbers. New orders append to the end, a phoned-in rush order inserts at index 0, the cook pops index 0 to start the next pizza, and a cancelled order is removed by its number. At closing time the display reads index 0 for the next order, and on an empty list that read would crash the screen.

Appending each new order keeps arrival order, so the list itself is the queue.
Inserting the rush order at index 0 shifts every waiting order right by 1, which the display must re-read.
pop(0) returns the order number the cook needs while shrinking the list, where del would lose it.
remove by order number fails if the customer cancelled twice, so a count check guards it.
A len() check before reading index 0 turns the closing-time crash into a message that says the kitchen is clear.
KEY TAKEAWAYS
A list, written as square brackets around comma-separated values, keeps them in order. len() counts them.
Indexes start at 0, negative indexes count from the end, and an index past either end raises IndexError.
items[i] = value replaces an item in place without changing the length.
append adds at the end and insert(index, item) adds at a position, shifting later items right.
remove deletes by value, pop deletes by index and returns the item, del deletes by index and returns nothing.
Checking the length before the risky call keeps the program running, and so does an emptiness check or a count check.

Lists hold values in order, so you can read, replace, add and remove them by position or by value.

Category
Python
Difficulty
beginner
Duration
46 minutes
Challenges
20 hands-on challenges

Topics covered: Creating Lists, Accessing Items by Index, Adding Items to Lists, Removing Items from Lists, Safe List Access

Lesson Sections

  1. Creating Lists (concepts: Lists)

    A list holds any number of values in one variable, in the order you wrote them. Where a plain variable stores 1 score, a list stores 3 scores or 300 of them and keeps track of which came first. Python writes a list as square brackets around values separated by commas. Items keep their order A list remembers the sequence you gave it. A for loop visits each item from first to last, the same way it walks through range, except the loop variable receives the item itself rather than a count. The list

  2. Accessing Items by Index

    Every item in a list sits at a numbered position called its index, and the numbering starts at 0. The first item is at index 0, the second at index 1, and the last item of a list with 4 items is at index 3. To read an item, write the list's name followed by the index in square brackets. Counting from the end A negative index counts backwards from the end. Index -1 is the last item, -2 the one before it, and -4 is the first item of a 4-item list. This saves working out the length first: the last

  3. Adding Items to Lists (concepts: List Modification)

    Lists change size while a program runs. The append method adds 1 item to the end, in place: the list itself grows and no new list comes back. Call it with a dot after the list's name and the new item in parentheses. After 3 calls, shopping holds 5 items, each new one landing after the previous. The order of the calls becomes the order in the list, so append is the right tool whenever items arrive one at a time and arrival order is the order you want to keep. Building a list in a loop The most co

  4. Removing Items from Lists (concepts: List Modification)

    Each way of taking items out of a list differs in what you give it and what you get back. remove takes a value and returns nothing. pop and del both take an index, but only pop hands the removed item back. Pick by what you know: the value, or the position. remove: by value groceries held "eggs" twice, and only the first one went. remove searches from the front, deletes the first match and stops. A second remove("eggs") would delete the other one, and a third would fail, because the value is no l

  5. Safe List Access (concepts: List Modification)

    Reading an index that doesn't exist, popping from an empty list and removing a value that is not there all stop a program. Each has a check that costs 1 line and runs before the risky call, so the program prints a message and carries on instead of ending with a traceback. Check the length before indexing A list of n items accepts indexes 0 through n - 1. An index is safe when it's below len(items), and, for counting from the front, at least 0. Compare first, read second. The else branch prints a