A while loop repeats for as long as a condition holds, which is exactly the tool you want when the number of repetitions is not known in advance and exactly the tool that hangs your program when you get it wrong. The difference between the two outcomes is not the syntax, which is trivial, but the discipline of making sure the loop always has a way to finish.
When is a while loop the right choice?
Reach for while when the thing you are looping over does not exist yet. Retrying a flaky network call, reading lines until a sentinel, waiting for a condition to become true, counting down to zero: in each case the number of iterations is decided by the data, not by you.
count = 0
while count < 3:
print(f'tick {count}')
count += 1
print('done, count is', count)
# The classic mistake: a condition that can never become false.
# Leave this commented out or your program runs forever.
# while count > 0:
# print('this line never stops')
The commented block is the failure mode worth drilling into. A while loop makes no promise about how many times it runs, so the burden falls entirely on you to make sure that something inside the body eventually changes the condition. Every while loop you write should have three parts in your head: the condition, the thing that changes it, and the guarantee that the change happens.
As a rule of thumb, if you can write the loop as "for each item in this collection", use for. The while version asks you to manage an index, a comparison and an increment, and every one of those is a chance to be off by one.
How do you break out of a loop safely?
Three keywords control a loop body. break leaves the loop immediately. continue skips the rest of the current iteration and starts the next one. pass does nothing at all, and exists so you can write a placeholder body without inventing a filler statement.
for number in range(1, 11):
if number == 6:
print('skipping 6')
continue
if number == 9:
print('stopping before 9')
break
print('processing', number)
print('the loop finished')
The while True idiom is the standard way to write a loop whose exit condition is checked inside the body rather than in the header. It is honest about what is happening: keep going until something inside the loop says stop.
outcomes = ['timeout', 'timeout', 'ok']
MAX_ATTEMPTS = 5
attempt = 0
while True:
if attempt >= len(outcomes):
print('no more attempts available')
break
attempt += 1
outcome = outcomes[attempt - 1]
print(f'attempt {attempt}: {outcome}')
if outcome == 'ok':
print('succeeded on attempt', attempt)
break
Notice the guard at the top. A retry loop that can fail forever is a denial-of-service bug against yourself, so cap the attempts and make the failure path explicit. The same pattern applies to a loop that waits for input: give it a maximum number of tries, and decide what happens when they run out.
How do you build an input loop that terminates?
Interactive loops need three things: a way to leave, a way to reject bad input, and a guarantee that the user cannot trap themselves in it. The usual design is a sentinel value that ends the loop, combined with validation that asks again rather than crashing.
answers = ['42', 'not-a-number', '7', 'quit']
index = 0
while index < len(answers):
raw = answers[index]
index += 1
if raw == 'quit':
print('leaving the loop')
break
try:
value = int(raw)
except ValueError:
print(f'{raw!r} is not a number, asking again')
continue
print('accepted', value)
The list here stands in for the keyboard, so the script runs to the end without waiting. In a real program the same loop reads input() instead, and you would also catch EOFError so that a closed input stream ends the loop instead of raising an exception.
There is one more failure mode to design for: a user who types nothing at all. If the input can be empty, check for that explicitly before converting it, because int('') raises ValueError just as int('abc') does, and an empty line is a far more common accident than a typo.
There is a neater way to express "read until a sentinel" when the items come from a function rather than a prompt. iter(callable, sentinel) calls the function repeatedly and stops when it produces the sentinel value.
def make_reader(items):
"""Return a callable that hands back the next item each time it is called."""
remaining = list(items)
def reader():
return remaining.pop(0)
return reader
reader = make_reader(['first', 'second', 'STOP', 'third'])
for line in iter(reader, 'STOP'):
print('processed', line)
print('the loop ended at the sentinel')
The word STOP is never yielded to the body, so the loop sees only the values it should act on. The same idea has a shorter form when you already have a list: iter(['first', 'second', 'STOP'], 'STOP') walks the list and stops at the marker. This pattern shows up constantly in parsers that read until a blank line or a marker.
What is the else clause on a while loop?
Python allows an else block on both for and while loops. It runs when the loop ends normally, which means the condition became false or the iterable was exhausted, and it did not run when you left with break.
attempts = [5, 3, 8]
for attempt in attempts:
if attempt > 7:
print('too big, stopping early')
break
print('trying', attempt)
else:
print('every attempt was within the limit')
print('---')
for attempt in [5, 3, 8]:
if attempt > 7:
print('too big, stopping early')
break
print('trying', attempt)
Both loops look almost identical, and only one prints the "within the limit" message. In the first, no attempt exceeded the limit, so the loop ran out of items and the else block fired. In the second, the break skipped it. That makes the construct a compact way to say "the loop finished without finding anything wrong", which is exactly the shape of a search loop. The full treatment of that construct comes in the next lesson.
Your turn: build a guessing game with a safety net
- Write a guessing game where the secret number is 7. Use a
while Trueloop, read the guess withinput(), and print whether the guess is too low, too high or correct. - Add a maximum of five attempts. When they run out, print a message that names the number of attempts used and leave the loop cleanly.
- Handle a guess that is not a number: catch
ValueError, print a friendly message, and do not count that attempt against the player. - Add a sentinel so the player can type
quitand stop at any point, and make sure the quit path and the attempts-exhausted path print different messages. - Finally, rewrite the loop so it uses
forover a list of possible guesses instead ofwhile, and confirm the two versions behave identically for the same input sequence.
Loop patterns worth knowing
- Use
whileonly when the iteration count comes from the data; useforfor everything else. - Every
whileloop needs a condition, something that changes it, and a guarantee that the change happens. breakleaves the loop,continueskips to the next iteration, andpassis a deliberate no-op.- Cap retry loops with a maximum attempt count and an explicit failure path.
- The
elseclause on a loop runs only when the loop finished without abreak, which makes it a compact "nothing went wrong" signal. iter(callable, sentinel)is the tidy way to consume a stream up to a marker value.