While Patterns

We have seen several loop patterns related to for loops. This time, we will see general patterns that are useful for while loops. The while loop patterns vary much more widely than the for loop patterns since the power of a while loop is unbounded.

Numeric While

counter = 100
while counter > 1:
    counter = counter // 2
    print(counter)

We have already talked about how for loops are better for iterating over a range of numbers. However, one immediate advantage for while loops is when we need to iterate through numbers non-sequentially. In the code shown here, instead of decreasing by one each time, we are instead halving the value in counter. This demonstrates exponential decay and will decrease much more rapidly down to a number less than one.

Any kind of numeric condition can be used with while loops, making them more flexible with numbers than for loops. For example, you could double the numbers each time (exponential growth), add a value based on user input or increase by a randomly generated number. Although this is more complicated, there is no replacement when you need to iterate in arbitrary ways.

Check User Input Loop

user_input = input("What is your age?")
while not user_input.isdigit():
    user_input = input("Age must be a number! Please try again.")
age = int(user_input)
print("Your numeric age is", age)

End users are notoriously untrustworthy and they often make mistakes. A while loop can be used to safeguard user input and make sure that they are correctly entering data. The pattern repeatedly takes user input until a condition is satisfied. In the following example, we are repeatedly looping until we are given digits for the age variable.

Do-Until Loop

# Do-Until Loop
finished = False
while not finished:
    finished = end_condition_expression

The while loop may execute any number of times. If the conditional is initially false, the loop will execute zero times. Often, we want the loop to execute at least once before we check the condition. The Do-Until Loop pattern restructures the conditional logic of the while loop to delay the conditional check until the end of the first iteration of the loop.

Example of Do-Until Loop

# Original Loop
password = input("What is the password?")
password = password.strip()
while password != "frog":
    password = input("What is the password?")
    password = password.strip()

# Do-Until Loop
finished = False
while not finished:
    password = input("What is the password?")
    password = password.strip()
    finished = password != "frog"

As a concrete example, let’s compare two programs that get input from the user until they enter a password. In the original version of the loop, we must have the input expression before the loop (to give an initial value to password) and again inside the loop. If all we needed was to call input, that would not be so bad; but we also wanted to call the strip method (to remove whitespace) on the password. In the Do-Until Loop version, we only need to write the logic for processing the password once—inside of the loop where that processing logically belongs.

Avoiding Delayed Conditional

# Appropriate initial Value
password = "WRONG PASSWORD"
while password != "frog":
    password = input("What is the password?")

In the previous example, we could also have avoided the redundant input call by choosing an appropriate initial value for password that doesn’t get user input. For example, you could assign the empty string ("") or perhaps the garbage string ("WRONG PASSWORD"). That would allow you to keep the conditional check in the header of the while loop.

Benefit of Delaying the Conditional

def check_password(given_password: str) -> bool:
    if given_password == "frog":
        return False
    elif given_password.strip() == "frog":
        print("You added extra whitespace to the password.")
    else:
        print("You gave the wrong password.")
    return True

# 1) Wrong!
# Says the user gave the wrong password before they did.
password = "WRONG PASSWORD"
while check_password(password):
    password = input("What is the password?")

# 2) Correct!
# Delays the check until after the user attempts the password
failed = False
while not failed:
    password = input("What is the password?")
    failed = check_password(password)

However, relying on an initial value means the conditional check does occur once before the user even has a chance to enter a password. When password checking becomes more complicated, this becomes a problem. For example, in the following code, the helper function check_password checks if the given_password is "frog". If not, the function also checks to see if the user accidentally added whitespaces (by using the strip method to test if removing the whitespace makes the password correct). Either way, a helpful message is printed to the user indicating that that password was wrong. However, the first version of the while loop will incorrectly print this message even before the user has a chance to try entering a password! The second version correctly delays the check until the end.

Read-Evaluate-Print-Loop

command = ""
while command != "EXIT":
    # Read input from user
    command = input("Input a word, or write EXIT:")
    # Evaluate ...
    # Print the result
    print("You wrote", command)
    # Loop back to start of body

print("No more words!")

Another use case where a while loop is more useful than a for loop is when dealing with repeated user input. For instance, the Read-Evaluate-Print-Loop (or REPL) that powers the console uses a while loop. The pattern shown here is one way to handle repeated user input. This repeatedly asks for words until the string "EXIT" is given, at which point it will exit the loop.

The Evaluate of the REPL

command = ""
value = 5
while command != "stop":
    # Read
    command = input("Write either increase, decrease, or stop")
    # Evaluate
    if command == "increase":
        value += 1
    elif command == "decrease":
        value -= 1
    # Print
    print("The value is now:", value)
    # Loop!

The “Evaluate” step of the REPL shown above is basically empty; we take a command from the user and print the command back unmodified. However, this kind of loop is frequently used to handle commands from the user. For example, the following program allows the user to write either “increase”, “decrease”, or “stop” to manipulate the value variable.

Main Game/Server Loop

One more kind of user-interaction loop you will occasionally see—but rarely write—is the Main Game Loop. This kind of loop pattern is also sometimes used for web servers and other long-running programs. Essentially, this is a loop that is never supposed to end. Some programs, such as games or websites, are not really supposed to stop accepting user input. The user is expected to just keep interacting with the game until the game/application/or computer is shut down. Therefore, the loop uses the unconditional value True for its condition, meaning its body will repeatedly execute forever. You will rarely ever need to create such a program yourself. Almost always, other software or libraries will handle this kind of loop for you, and you will just be expected to write the code that will be executed in the body of the loop. However, you should be aware that these kinds of infinite loops exist and can be useful in specific cases.

Random Looping

from random import randint

first_number = randint(0, 10)
second_number = randint(0, 10)
while first_number == second_number:
    second_number = randint(0, 10)

print(first_number, second_number)

Another interesting use case for a while loop is to repeatedly pick random numbers. This is less of a specific pattern and more of a general use case, but the principle is the same each time. Basically, we use random (also known as stochastic) processes to repeatedly try solutions until one satisfies our constraint. In this example code, we want to pick two different numbers, so we call the randint function twice. If the second number is the same as the first number, we repeatedly reroll the second number until the two numbers are different. There are often ways to achieve the same effect using complicated math, but a while loop can be a straightforward solution. Of course, in the worst-case scenario, we might end up rolling badly each time and waste a lot of time waiting to find a good number.

List While

targets = [2, 3, 5, 4, -1, 1]
index = 0

while targets[index] != -1:
    index = targets[index]
    print(index)

Just as we saw that a while loop can replace a for loop for processing numbers, we can also use the while loop to process a list. This allows us to process the list in interesting ways that previously were not possible with a simple for loop, which just iterates through the sequence from start to finish. Now, we can jump to different places in the list. As before, however, you must be careful to avoid accidentally looping forever or indexing out of bounds. Remembering to update the current index is very important. Before you run this program, try to predict what will be printed.

Stepping through the List While Example

  1. index: 0
  2. index: 2
  3. index: 5
  4. index: 1
  5. index: 3
  6. index: 4
  7. index: -1

Let’s walk through the example above:

The initial value of index is 0. The conditional checks if the value at that index is equal to -1. Since the value at index 0 is 2, the conditional is true and the loop body executes. Next, the index variable is updated to be the first element of targets, which is again 2. That value is printed and the loop restarts. Then the expression targets[index] is calculated using 2 as the index value, which means the third element (5). Since that value is not -1, the loop continues. Now the index variable is again updated based on the value at the relevant index of targets; this time the value is 5. That value is printed and the loop restarts. Next, the conditional check looks at index 5 of targets, which is 1 (the last element of the list). That is not -1, so the loop continues. Here, the index variable is updated to be 1, which was the element at index 5. That value 1 is printed and the loop restarts. This process continues until eventually index is 4, which is where the -1 is stored in the list. At that point, the conditional expression targets[index] evaluates to -1 and the loop terminates.

This example is meant to demonstrate just how complex while loop iteration can be. Unlike a for loop, the lists elements do not have to be processed in order, and elements might even be skipped. You must be careful to trace while loops very carefully.

Summary