Skip to main content

Lists: collections of things

Up to now, every variable you have made holds exactly one value: one number, one piece of text, one answer. But real problems are rarely about a single thing. You have a row of test scores, a queue of customers, a shopping cart of items. For that you need a list: a single name that holds many values, lined up in a row and kept in a fixed order. Once you can store many things in one variable and walk through them one at a time, you can solve a huge share of all programming problems — because, as you will see again and again, almost every coding task is really "take a list and do something to it."

Let's define the words carefully, because we will use them constantly. A list is an ordered, changeable collection of values stored in one variable. Ordered means the items have a fixed left-to-right sequence and that order is remembered. Changeable (the formal word is mutable) means you can add, remove, or overwrite items after you create the list. A collection just means "more than one value grouped together."

You create a list by writing the values inside square brackets [ ], separated by commas. The empty pair [] is a list with nothing in it yet. That one name now refers to all of the values at once:

nums = [3, 5, 9] # a list of three numbers
names = ["Ada", "Bo"] # a list of two strings
empty = [] # a list with zero items

A list can hold a mix of types — [1, "two", 3.0] is perfectly legal in Python — but in practice you almost always keep one kind of thing in a list (all numbers, or all names). That makes your code predictable: if you know every item is a number, you know you can add them all up without surprises.

note

Two terms you'll hear forever: an element (also called an item) is one single value sitting inside the list. The index is the numbered position of an element. Counting starts at 0, not 1 — so the very first element lives at index 0. This is the single most common beginner trip-up, so say it out loud: the first element is at index zero.

Why does counting start at 0 instead of 1? Think of the index as "how far from the front," not "which one." The first element is 0 steps from the front, the second is 1 step in, the third is 2 steps in, and so on. It feels strange for about a week and then becomes second nature. Here is a picture of nums = [3, 5, 9] with its indexes written underneath:

value: 3 5 9
index: 0 1 2

To read a single element, write the list name followed by the index in square brackets. This is called indexing:

nums = [3, 5, 9]
print(nums[0]) # 3 (the first element)
print(nums[1]) # 5 (the second element)
print(nums[2]) # 9 (the third element)

Python also lets you count from the end using negative indexes. -1 means "the last element," -2 means "the one before last," and so on. This is handy because you get the last item without first knowing how long the list is:

nums = [3, 5, 9]
print(nums[-1]) # 9 (last element)
print(nums[-2]) # 5 (second from the end)

To find out how many elements a list has, use the built-in len function (short for "length"). You pass the list in the parentheses and it gives back a count:

nums = [3, 5, 9]
print(len(nums)) # 3
print(len([])) # 0 (the empty list has zero items)
tip

Here is a fact worth burning into memory, because it is the source of countless bugs. Since indexes start at 0, the last valid index is always len(nums) - 1, not len(nums). For nums = [3, 5, 9], len is 3, so the last index is 2 and nums[2] is 9. Asking for nums[3] is one past the end, and Python stops with an IndexError — its way of saying "there is nothing at that position." Being off by exactly one in either direction is so common it has a name: the off-by-one error.

Because a list is changeable, you can edit it after it exists. The simplest change is to overwrite one element by assigning to its index:

nums = [3, 5, 9]
nums[1] = 7 # replace the element at index 1
print(nums) # [3, 7, 9]

To make a list grow, the workhorse is append, which adds one element onto the end. Notice the dot syntax: nums.append(12) means "tell the list nums to do its append action with the value 12." (An action attached to a value with a dot like this is called a method.)

nums = [3, 5, 9]
nums.append(12) # nums is now [3, 5, 9, 12]
nums.append(4) # nums is now [3, 5, 9, 12, 4]
print(nums) # [3, 5, 9, 12, 4]
note

Three more list methods you'll see, in one breath: nums.pop() removes and hands back the last element; nums.remove(5) deletes the first element that equals 5 (by value, not by position); and nums.insert(1, 99) puts 99 at index 1 and shifts everything after it one step to the right. You don't need to master these now — just know they exist, so you recognise them in the wild.

Often you just want to ask, "is this value somewhere in the list?" The keyword in answers exactly that, giving back True or False. This is called a membership test:

nums = [3, 5, 9]
print(5 in nums) # True (5 is in the list)
print(7 in nums) # False (7 is not)

Sometimes you want a chunk of a list rather than one element. That chunk is called a slice, and you ask for it with two indexes separated by a colon: nums[start:end]. The crucial rule: start is included, end is excluded — you get everything from start up to but not including end. So nums[1:3] gives the elements at indexes 1 and 2, never 3:

nums = [3, 5, 9, 12]
print(nums[1:3]) # [5, 9] (indexes 1 and 2)
print(nums[:2]) # [3, 5] (leave start blank = from the beginning)
print(nums[2:]) # [9, 12] (leave end blank = through the end)

Leaving a side blank means "go all the way to that edge." And one bonus trick to file away: nums[::-1] walks the list backwards and so returns a reversed copy — for the list above that is [12, 9, 5, 3].

Now the heart of the lesson. The single most common thing you do with a list is visit every element in turn and do something with each one. That repeated visiting is called a loop. A for loop hands you one element at a time and runs the indented code once per element. The cleanest way is to loop directly over the list:

nums = [3, 5, 9]
for n in nums:
print(n)
# 3
# 5
# 9 (one element per line)

Here n is a temporary variable — you can name it anything. On the first pass n is 3, on the next pass it becomes 5, then 9, and when there are no more elements the loop ends. Looping over the list directly like this is preferred because there are no indexes to get wrong: no off-by-one mistakes, no IndexError, and the code reads almost like English ("for each n in nums...").

Occasionally you genuinely need the position of each element, not just its value — say, to print "item #1, item #2." One way is to loop over the indexes using range(len(nums)), which produces the numbers 0, 1, 2, ... up to the last valid index:

nums = [3, 5, 9]
for i in range(len(nums)):
print(i, nums[i])
# 0 3
# 1 5
# 2 9

A tidier tool for that same job is enumerate, which hands you the index and the element together on each pass, so you skip the manual nums[i] lookup:

for i, n in enumerate(nums):
print(i, n) # 0 3 then 1 5 then 2 9

Let's put everything together in a worked example you'll reuse forever: finding the largest value in a list. The idea is the accumulator pattern — keep one variable holding the "best answer so far," look at every element, and update that variable whenever you find something better:

def largest(nums):
biggest = nums[0] # assume the first element is the biggest
for n in nums:
if n > biggest:
biggest = n # found a bigger one — remember it
return biggest

print(largest([3, 5, 9, 2])) # 9

Trace it slowly, the way an interviewer wants you to. We start with biggest = 3 (the first element). The loop then visits:

  • n = 3: is 3 > 3? No. biggest stays 3.
  • n = 5: is 5 > 3? Yes. biggest becomes 5.
  • n = 9: is 9 > 5? Yes. biggest becomes 9.
  • n = 2: is 2 > 9? No. biggest stays 9.

The loop runs out of elements, and we return biggest, which is 9. Notice we started biggest at nums[0] rather than at 0 — if the list were all negatives like [-5, -2, -8], starting at 0 would wrongly report 0 as the max. Choosing the right starting value is part of getting the accumulator pattern right.

tip

This "loop once, keep a running answer" shape is the backbone of nearly every algorithm lesson later in this guide. When you see "find the max," "find the min," "add up the total," "count how many," or "find the first match," it is almost always a single for loop over a list with one accumulator variable doing the bookkeeping. Master this one pattern and a surprising amount of the rest is variation on a theme.

Why it matters

A list is the first of three collections you'll lean on constantly. It is perfect when order matters and you reach things by position. Next you'll meet two relatives: the dictionary, which looks things up by a key (like a name) instead of a number, and the set, which keeps only unique values for instant "have I seen this?" checks. Together, list + dict + set plus a single loop quietly solve most of the problems you'll ever be handed.

Where this leads: lists plus loops become powerful with patterns like two indices moving toward each other. See Where this leads next.

Practice

Loop a list and track a result — run it live:

⌨️ Challenge — Python · practice (not graded)

Write max_of(nums) that returns the largest number in the list nums — without using the built-in max(). Use the accumulator pattern: start best at the first element, loop the rest, and update best whenever you find something bigger.

Checkpoint

Required checkpoint

Lists: collections of things

Pass to unlock the Next button below

Next: Dictionaries & sets →