2 | Loops, Lists and More Fundamentals

In your first project, you learned about many of the fundamental topics in coding. You’ll need the tools and methods you used to write the Angry Goblin game for any coding you’ll do in the future. In this Chapter, you’ll explore more of the key building blocks you’ll need to write computer programs, including more loops and lists.

But before you move on to learn about new tools, I’d like to introduce you to The White Room.

The White Room

Try to picture an empty room with white walls and white ceilings. This White Room represents a computer program. The actions that happen in a computer program all occur in this room. Any resources that are needed to perform these actions must also be in the room. On one of the four walls, there are several shelves.

When you open a new, blank Python file in your editor, you create a new White Room. Although your Python file is still empty, the White Room is not entirely empty. On the lowermost shelf, there is a small, red booklet called ‘built-in’. This booklet has a relatively small number of Python words in it to get you started. It includes essential functions and keywords that you have access to right away, such as print() and input(), and if and while.

The program must be able to find a reference to any word you write in the program. The word needs to be somewhere in the White Room. For the time being, the only place Python can look in is the small, red booklet called ‘built-in’, as this is the only item in the White Room.

You can open a new file and write:


There is no output when you run the above program, but behind the scenes, the computer program is looking around the White Room to try and find a reference for the word print. It will find it in the ‘built-in’ booklet, and therefore the program will be satisfied that it found this word. Python knows what this word means as it has been able to find it in the White Room. Now try the following:


When you run this code, you’ll get the following error message:

Traceback (most recent call last):
  File "<path>/<filename>.py", line 1, in <module>
NameError: name 'say_hello' is not defined

You’ll see errors often when you code, and we’ll discuss these further later on. The error message tells you that the computer program looked everywhere in the White Room and couldn’t find the word say_hello anywhere. The last line of the error message tells you that name 'say_hello' is not defined. At the moment, the only thing in the White Room is the ‘built-in’ booklet, and the word say_hello is not in it!

When you created variables in the first chapter and assigned data to the variable, I described this as creating a storage box. Let’s look back at that analogy with the following:

say_hello = "Hello there, how are you doing today?"

You’re asking your program to bring an empty storage box, put a label on the outside with say_hello on it, and then put some information inside the box. In this case, the information you put inside the box is a string with several characters. You can now place this box on one of the empty shelves on the wall, with the label facing outwards.

From now on, when you write say_hello in your program, the program will look around the White Room, and it will find something with the label say_hello. It’s a box with some contents inside it. Therefore the computer program will fetch the box and get whatever is inside it, which is the string "Hello there, how are you doing today?".

For the time being, I’ll make one more addition to the White Room. Another analogy I used in Chapter 1 is that of a large library with thousands of books. You can import a module using import. The import keyword is one of the words in the ‘built-in’ booklet:

import random

You’re asking your computer program to leave the White Room, go to the big library and look for a book called random, then bring it back to the White Room and put it on one of the shelves. From now on, when you type the word random, your program will find a book called random in the White Room. When you type random, you’re asking the program to bring the book down from the bookshelf. You can then look inside the book using the dot notation: random.randint().

We’ll come back to The White Room analogy from time to time in this book to help understand what’s going on inside a computer program.

Writing DRY Code

There is a joke which I’ll share below. There are many jokes in the programming world, but the general programming sense of humour is a bit peculiar. You’ve been warned.

The ten commandments in programming are:

  1. Don’t Repeat Yourself
  2. Don’t Repeat Yourself
  3. Don’t Repeat Yourself
  4. Don’t Repeat Yourself
  5. Don’t Repeat Yourself
  6. Don’t Repeat Yourself
  7. Don’t Repeat Yourself
  8. Don’t Repeat Yourself
  9. Don’t Repeat Yourself
  10. Don’t Repeat Yourself

Jokes aside, the Don’t Repeat Yourself principle is central to everything in programming, and it’s often referred to using the acronym DRY.

The DRY principle spans all aspects of writing code. It could refer to creating a loop to avoid repeating the same lines of code many times. It can also be applied to defining a variable that holds some information so that if you need to change that information later, you can do so easily. You’ll find out that one of the key benefits of many tools and constructs in programming is to avoid repetition and be efficient.

You’ll often hear about code that needs to be more DRY. You’ll see that there are often many ways of achieving the same goals in programming, but some options are more DRY than others. DRY code is easier to maintain and less likely to lead to bugs.

You may also hear about WET code. WET code is code that’s not DRY! And here’s the programmers’ sense of humour shining through again with attempts to retrofit the acronym to create a ‘backronym’ for WET:

  • We Enjoy Typing
  • Write Everything Twice
  • Waste Everyone’s Time

We will refer to the DRY principle often.


One of the most obvious ways of keeping our code DRY is by using loops. A loop repeats a block of code. It’s fine for the computer program to repeat itself as it’s fast and doesn’t get bored. What we want to avoid is that we, the programmers, don’t repeat ourselves.

You’ve already used one of the main ways of creating a loop in Python. In the Angry Goblin game, you’ve used the while loop to repeat a block of code until the player guesses the goblin’s position. There is a second essential tool for creating loops in Python: the for loop.

The for Loop

You want to shout out from the rooftops one of the important principles in coding, so you write the following:

print("Don't Repeat Yourself")

But this statement is so crucial that you want to repeat it 10 times and replicate the famous programming joke. Option 1 would be to copy and paste the line 10 times. But that would not follow the DRY principle. Instead, you can use a for loop. Let’s create the for loop, and then we’ll break down what’s happening step by step:

for something in range(10):
    print("Don't Repeat Yourself")

When you run this code, you’ll see the line displayed 10 times. Let’s break down these two lines of code to see what everything does:

  • The keyword for is one of the keywords in the ‘built-in’ booklet that indicates we want to write, you guessed it, a for loop.
  • The function range() is another built-in function that returns a range of numbers. In this case, range(10) gives us ten numbers. You’ll see which numbers these are soon.
  • The colon : at the end of the first line and indent at the start of the second are aspects of Python’s syntax you’ve seen already when writing the while loop and the if statement.
  • How about something? Is this a built-in command too? The answer is no. Let’s see what something really is.

The word something was not present in the White Room before you wrote it in the for loop statement. It’s a variable you’re creating when you write it down in the line for something in range(10):

The variable something is the same type of box as when you create a variable using the assignment operator = as you did in the first chapter. To convince you that there’s nothing special about the name something, you can try the following versions:

for pizza_with_cheese in range(10):
    print("Don't Repeat Yourself")

Or indeed:

for vcnxzbvjihafynariayenmsx in range(10):
    print("Don't Repeat Yourself")

Both of these versions will give the same output. This begs the question: If it doesn’t matter what this word is, why do I need to put it in? Let’s see what’s being stored in this box by printing its contents:

for something in range(10):

The output of this code is the following:


The print() function is repeated 10 times as it’s in a for loop. However, it prints a different value each time. The content of the box labelled something is changing each time the loop repeats. The numbers from 0 to 9 are the values given by range(10). They’re not quite the 10 numbers a human might have come up with as we usually start counting from 1, but many coding languages start counting at 0. You’ll come across this quirk often and will, eventually, get used to it.

Let’s use this feature to try and show the numbers 110 at the start of each line that says “Don’t Repeat Yourself”. You’ll need to create a string that looks like this: "1. Don't Repeat Yourself". However, the number will need to change each time the for loop repeats.

In the first chapter, we had combined two strings by using the + operator to concatenate the strings. Here, you’ll use a better way of formatting strings:

for something in range(10):
     print(f"{something+1}. Don't Repeat Yourself")

Have you spotted the extra f just in front of the open quotation mark? This f indicates that you’re using a special type of string called an f-string, which stands for formatted string. There’s also a pair of curly brackets {} in the f-string. Whatever is inside the curly brackets is evaluated as a Python statement first, and then the result is placed instead of the {} in the string. When the for loop goes through the indented lines for the first time, the variable something has the value 0. Therefore something + 1 evaluates to 1. Each time the loop repeats, this value will increment by 1.

The output from the code above is:

1. Don't Repeat Yourself
2. Don't Repeat Yourself
3. Don't Repeat Yourself
4. Don't Repeat Yourself
5. Don't Repeat Yourself
6. Don't Repeat Yourself
7. Don't Repeat Yourself
8. Don't Repeat Yourself
9. Don't Repeat Yourself
10. Don't Repeat Yourself

Each line has been numbered! You’ll come across f-strings again in this book. They are a relatively new addition to Python as they were introduced in Python 3.6 a few years ago.

You can repeat a block of code any number of times using the for loop together with the built-in function range().

Revisiting The while Loop

You’re now familiar with the two types of loops in Python, the for loop and the while loop. If both of them repeat a block of code, which one should you use?

In most cases, when you need to repeat a block of code, you can write your code in such a way so that you can use either a for loop or a while loop. However, each type of loop is best suited for specific uses.

When you want to repeat a block of code a fixed number of times, the for loop is the tool for the job.

If you don’t know how many times you should repeat a code block and you rely on the computer program to decide, based on what’s happening in the program, then the while loop is best. In the Angry Goblin game, this was the reason why you used a while loop. The loop had to keep repeating until the player guesses.

Even though for loops and while loops are similar in one sense—they both repeat code—they are pretty different in the way we use them. The while keyword needs to be followed by a statement that Python can interpret either as True or False. If the statement following while is True, then the code block will run and then go back up to the line with the while statement. The loop will keep repeating until the statement becomes False.

You can try and experiment with the while loop with short examples such as the following:

import random

a_number = 0

while a_number <= 20:
    a_number = random.randint(1, 25)
    print(f"This is the random number: {a_number}")

print("You have found one larger than 20")

Let’s break down this code using the White Room to help understand how the computer sees this program:

  • import random
    • You create a new White Room when you first open the file, and then you import random. The computer program goes off to the library to fetch the random book and bring it to the White Room.
  • a_number = 0
    • You ask the program to get a box, label it a_number, and put the integer 0 inside the box.
  • while a_number <= 20:
    • The while keyword is in the ‘built-in’ booklet. The booklet has the ‘rules’ which the program should follow when dealing with a while loop.
    • When you write a_number, the program finds a box with that label, brings it down from the shelf and gets its contents out. The box currently contains the number 0.
    • As the program reads the rest of this line, it will next find the <= operator. The program will compare 0 with 20. It decides that the answer is True as 0 is smaller than 20.
  • Since while is followed by True, the indented code after the while statement will now run.
  • indented block
    • The program now moves on to another assignment. It will first consider the right-hand side of the = operator. The program brings the random book down from the shelf, opens it and looks for the word randint. The randint() function will then do its thing. It will pick a random number between 1 and 25. Whatever number is chosen will be stored in the box.
    • There is an issue that needs to be resolved. There’s already a box labelled a_number on the shelves in the White Room. Computer programs cannot have the same name used more than once as they won’t know which one to fetch. So the current contents of the box a_number are thrown away in the bin. The bin in the White Room is a special one. It empties itself very quickly. So the old contents of the box are now gone and cannot be recovered. The new number is stored in the box, and the box is placed back on the shelf.
    • The print() function will print the number chosen once it fetches the contents of the a_number box.
  • As this is the end of the code in the while loop, the program will go back up to the line with the while statement. But now, when it fetches the box a_number, it will no longer find the integer 0, which was there initially. The box will now contain whichever number randint() chose. The <= operator will check the two numbers and issue a verdict: True or False.
  • As long as the number in the box is less than or equal to 20, the code in the loop will keep repeating. At some point, randint() will choose a number larger than 20. When this happens and the program returns to the while statement, this will evaluate to False. The program will therefore skip the code in the loop. It will move on to the next line of code following the loop, which is the next line that doesn’t have the indent.
  • The program will print out the final statement, and the program ends. The White Room can now be dismantled!

Here’s the output from the code:

This is the random number: 12
This is the random number: 17
This is the random number: 7
This is the random number: 17
This is the random number: 24
You have found one larger than 20

Each time the program is run, the numbers will be different. The number of repetitions will also be different. Try running the code a few times to see the different outputs you get.

Wearing Two Hats: Programmer And User

When you use computers daily, you’re using software that other programmers have written. You are the user of the software, and you don’t care what the code looks like. Often you can’t know anyway as the code will be proprietary. You can only do whatever the programmer wants you to do. When you click a button, something happens, and that ‘something’ is what the programmer decided would occur when that button is clicked.

As a programmer, you’re sitting on the other side of the divide. You are the one deciding what happens. You may be writing code for others to use, or perhaps for yourself. Either way, while writing the program, you need to wear both hats, that of programmer and user. When you run your program, you’re acting as a user while you try it out and test it. You’ll then go back to the code and resume your programmer role.

It is useful to distinguish the two roles. For example, if you’re writing a game for the user to guess a number, as was the case with the Angry Goblin game, your code will have the following line:

goblin_position = 3

When you run the program to try it out and put your user hat on, you’re not aware of the line above. The user does not know what the code says. To test your program, you should pretend you don’t know the number. That way, you can see what happens when the user responds incorrectly. You also need to test what happens when you guess correctly, of course.

Sometimes it is important to remember this programmer-user distinction when writing and testing code.


You’ve already seen several examples of indentation in Python. Indents are an essential part of Python’s syntax. They’re not there just to make the code look neat.

An indent is a way of grouping some lines of code together. All of the statements you’ve seen that end with a colon must always be followed by at least one indented line:

import random

a = random.randint(1, 10)

if a == 5:
    print("a is 5, after all")
    print("great news")
print("Have a good day")

The two indented lines belong together. If a is equal to 5, then both lines will be executed. Otherwise, both lines will be ignored.

The last line has no indent, so it will always happen.

It’s helpful to become familiar with some error messages you’ll get when there’s something wrong with the indents:

import random

a = random.randint(1, 10)

if a == 5:

print("Have a good day")

When you run the code above you’ll get the following error message:

  File "<path>/<filename>.py", line 7
    print("Have a good day")
IndentationError: expected an indented block

Python is telling you it’s expecting a block of code that has an indent following the if statement. You need to include at least one indented line after a line ending with a :

Here’s another indentation error:

import random

    a = random.randint(1, 10)

if a == 5:
    print("a is 5, after all")
    print("great news")
print("Have a good day")

This gives the following:

  File "<path>/<filename>.py", line 3
    a = random.randint(1, 10)
IndentationError: unexpected indent

You may have noticed that Python identifies different types of errors. The last line in the error message starts with the error type. In these examples, all these errors are an IndentationError. When you get an error message you should also look at the line number which is displayed in the first line of the error message. This line number is not always exactly where the error is, but it will always be very close!

And here’s one last IndentationError:

import random

a = random.randint(1, 10)

if a == 5:
     print("a is 5, after all")
    print("great news")
print("Have a good day")

Did you spot the error? Python is very picky with making sure things are tidy. An indented block of code must have the same amount of indentation. In this case, the two lines don’t have the same indent. The error you get is the following:

  File "<path>/<filename>.py", line 7
    print("great news")
IndentationError: unindent does not match any outer indentation level

If you’re using an IDE such as PyCharm, you’ll have noticed that when you press Enter/Return after a line ending with a colon, the indent is added automatically. IDEs are there to help you and to make your life easier. And while I’m talking about IDEs making your life easier, you’ll also have noted the autocompletion done by your IDE. If you start typing the first few letters of a word, the IDE will suggest options, and you can press Enter/Return to autocomplete. Do make the most of these helpful features.

You’ll often have more than one level of indentation. Here’s an example of this:

import random

a = random.randint(1, 20)
b = random.randint(1, 20)

if a > 10:
    print("a is a big number, it seems")
    if b > 10:
        print("Whoa, both a and b are in double digits!")

The block of code that ‘belongs’ to the first if statement, which checks whether a is greater 10, has three lines. One of these lines, the final one, is also part of another if conditional statement, the one checking whether b is greater than 10. The last line has two indents: one makes it a part of the first if statement and the other also makes it a part of the second if statement.

Try making a small change to the program above so that you print the values of a and b, then run the code several times. You should notice that if a is greater than 10, the line that says that a is a big number will be printed. If both a and b are greater than 10, both statements are printed out.

However, if b is greater than 10 but a is not, nothing will be printed out. This is because the second if statement is part of the code block that’s conditional to the first if statement. The second if statement needs the first one to be True for it to be considered.


So far, you’ve used relatively simple data types in the examples you’ve programmed so far. You’ve used strings to store text, integers or floats for numbers, and you’ve used the Boolean data type to represent a true-or-false value.

Often, you’ll have items that belong together. For example, if you wanted to store the names of your four team members at work, you could think of creating four variables:

name1 = "John"
name2 = "Kate"
name3 = "Jason"
name4 = "Mary"

You’ve created four separate storage boxes that are now on the shelf in the White Room. You know that these names are linked—they’re the names of all the team members. However, your computer program doesn’t know that, and it has no way of knowing that these four bits of information are linked. This point will make it difficult to treat them as a group in your code. It will lead to very WET code.

Python has a data type to deal with this situation. This data type is the list. You can create a list with the names of your team members:

team_members = ["John", "Kate", "Jason", "Mary"]

The square brackets following the assignment operator = tells Python that the data being stored in the box is a list. Each item within the list is separated with a comma. The four names are now stored in a single storage box labelled team_members. The individual strings are the first, second, third and fourth items in the team_members storage box.

The list you created above contains strings. However, you can store any data type within a list. You can even mix and match data types:

my_numbers = [4, 6, 12, 9, 6, 23]
quiz_answers = [True, True, False, True, False]
useless_stuff = ["Hello", 35.3, False, "Bye Bye", 1]

These three lines create a list of integers, a list of Booleans, and a list containing various data types.

Using Lists: Indexing And Slicing

You can now experiment with lists to learn how to extract values from them. You’ll use a Console session for this, which makes it easier to explore:

>>> my_numbers = [4, 6, 12, 9, 6, 23]
>>> my_numbers
[4, 6, 12, 9, 6, 23]

You’ve created a variable my_numbers, which stores a list, and then you asked Python to display the contents of this variable. You can choose to retrieve only one of the items within the list:

>>> my_numbers[1]

You’re using square brackets again. However, these square brackets are not standalone brackets. Instead, you’ve written them immediately after the name of the list. This notation means that you would like to extract an item from the list. We call this indexing.

The number retrieved when you typed my_numbers[1] was the second number in the list. What about the first one? You may recall from when you used range() that when Python counts numbers, it starts from 0 instead of 1. The first item in the list has the index 0:

>>> my_numbers[0]

You’ll get used to zero-indexing soon, but in the meantime, you may find it frustrating. You’re not alone! You can even use negative indices— indexes is also often used for the plural of index in coding:

>>> my_numbers[-1]

This notation gives you the last item in the list. It’s a helpful trick to remember, as you’ll often need to fetch the last item in a list.

Another way of extracting values from a list is by using slicing. Here’s what slicing looks like:

>>> my_numbers[2:5]
[12, 9, 6]

The notation you’ve used is similar to indexing, with the difference that you’ve included a range of numbers in the square brackets using the colon to separate the start and the end. The result of this slicing operation is another list with three values in it.

The numbers extracted from the original list are the third, fourth and fifth numbers in the sequence. The index 2 represents the third item in the list since the first index is 0. However, the index 5 represents the sixth item in the list, which is 23. The result of the slicing operation does not include this value. The reason for this is another quirk you’ll get used to quickly. When there’s reference to a range of numbers in Python, the start of the range is included, but the end is not. Therefore the slice 2:5 refers to the numbers from index 2 up to but excluding the index 5. This is a long-winded way of saying the indices represented are 2, 3 and 4.

There’s one more thing you can do with slicing. In addition to the start and end of the range, you can also decide to move in larger steps. You can create a longer list to experiment with slicing:

>>> more_numbers = [3, 67, 23, 1, 89, 4, 56, 2, 78, 4, 12, 8]
>>> more_numbers[2:10:2]
[23, 89, 56, 78]

The slice you’re using in the square brackets is now 2:10:2. The first and second numbers represent the start and end of the range as before. The final number is the step size. The slice extracts the items from index 2 up to but excluding index 10 in steps of 2. So you get the third, fifth, seventh, and ninth items from the list.

As programmers like to be lazy, there are some useful shortcuts:

>>> more_numbers[:7]
[3, 67, 23, 1, 89, 4, 56]

>>> more_numbers[4:]
[89, 4, 56, 2, 78, 4, 12, 8]

>>> more_numbers[4::2]
[89, 56, 78, 12]

>>> more_numbers[::2]
[3, 23, 89, 56, 78, 12]

>>> more_numbers[::-1]
[8, 12, 4, 78, 2, 56, 4, 89, 1, 23, 67, 3]

If you’d like to slice from the start of the range, you don’t need to write the 0 at the start. You can leave the value blank. In the first example, you extract the items from index 0 up to and excluding index 7 using the slice :7, which is equivalent to 0:7. The same rule applies to the end of the range. If you’d like to go to the end of the list, then the end number can be omitted, as in the second example above.

In the third slicing operation, the slice is 4::2. This notation indicates that you’re looking for items from index 4 to the end of the range in steps of 2. The value that’s been omitted is the second number in the slice which represents the end of the range.

The fourth example goes a step further. The slice used is ::2 and it extracts the whole range of indices but in steps of 2. The final example is a trick you’ll find very useful when you want to reverse a list.

You’ll learn later on that indexing and slicing are techniques that are not exclusive to lists. You can also use them on other data types that are sequences. But more on that later.

The for Loop Revisited: Iterating Through Lists

We’ll finish this Chapter by revisiting the for loop. You’ve seen how the for loop is useful when we need to repeat some code a certain number of times.

Let’s look at the team members example again from earlier in this Chapter:

team_members = ["John", "Kate", "Jason", "Mary"]

You would like to print out a greeting for each team member. You’d like to write DRY code, so you consider using the for loop. When you learned about the for loop earlier, the for statement looked like this:

for something in range(10):

The function range() produces a range of numbers, and the for loop is iterating over those numbers, assigning each one to the variable something, one at a time. The for loop can iterate over anything that Python recognises as a sequence. You can therefore replace the range() function with the name of a list since a list is a sequence of items:

team_members = ["John", "Kate", "Jason", "Mary"]

for person in team_members:
    print(f"Hello {person}. How are you doing today?")

In this short program, you’re first creating a box labelled team_members which is a list containing four strings. In the for statement, the program recognises team_members as a box that’s stored on the shelves. It also recognises it’s a sequence. Therefore, it can be used in a for statement. The variable person is created, and the first item from team_members is assigned to person.

The first iteration of the loop goes ahead, and the phrase is printed out with "John" replacing the placeholder {person} inside the f-string. The program now returns to the for statement and moves to the second item in the list. It assigns "Kate" to the variable person. The loop will keep iterating until there are no more items in the list.

This code gives the following output:

Hello John. How are you doing today?
Hello Kate. How are you doing today?
Hello Jason. How are you doing today?
Hello Mary. How are you doing today?

Looping, or iterating, directly over a list is a coding structure you’ll often use when coding in Python. You’ll learn later that you can also iterate over other data types, not just lists. However, not all data types can be used. You’ll find out there’s a lot more to say about data types, and there’s a whole chapter coming later on focusing on this topic.


In this Chapter, you’ve covered:

  • What really happens in a computer program through the White Room analogy
  • How to repeat code using a for loop
  • How to understand some common error messages
  • How to create lists
  • How to access items from a list using indexing and slicing
  • How to iterate through a list using the for loop

These first two chapters dealt with many of the fundamental topics in coding. The methods and tools discussed so far form the building blocks of any computer program you will ever write. There is one more essential tool in modern programming that will complete the set of fundamentals: defining functions. In the next Chapter, you’ll learn how to create your own functions, and you’ll work on another short project.

Additional Reading
  • You can read the article about creating a bouncing ball in Python which uses the turtle module to create simple animations
  • There’s also a blog post that introduces linked lists, which are a bit different from the lists you read about in this Chapter. The article talks about stacks and queues and why using lists may not necessarily be the best way to implement these structures.

Subscribe to

The Python Coding Stack

Regular articles for the intermediate Python programmer or a beginner who wants to “read ahead”


DRY Don’t Repeat Yourself is a key concept in programming. Several methods and tools in programming can be categorized using this concept.
f-string An f-string is a formatted string. It enables you to format a string using data from within your program. Curly brackets {} are used in f-strings to contain statements that will be executed and represented as part of the string.
List A list (list) is a type of data that stores individual items in a sequence. Lists can be modified by a program by removing, adding, or changing items in the list.
Index The index is the number that represents the position in a sequence, for example, the position of an item in a list or the position of a character in a string. Python uses a zero-indexing system which means that the first item in a sequence has the index 0.
Indexing Indexing is the process of extracting one item from a sequence of items using its index, that is its position in the sequence. Square brackets [] are used for indexing in Python.
Slicing Slicing is the process of extracting a subset of items from a sequence using a range of positions. Square brackets [] and colons : are used as part of the slicing notation in Python.