Python Module 3 – Control flow: decisions, loops and pattern matching
while loops, break, continue and loop else
Repeat code with Python's while loop, stop it at the right moment, use break and continue on purpose, and learn when the else clause of a loop runs.
What you will learn
- Write while loops with correct stopping conditions
- Use break and continue deliberately
- Explain when a loop's else clause runs
Before you start
On this page
A while loop runs a block again and again for as long as its condition is true. It is the loop to use when you
cannot say in advance how many times the work must happen: until the user types a sensible answer, until a number
reaches 1, until a search finds what it is looking for. (To go through the items of a list or a range of numbers, the
for loop of the next lesson is simpler.)
How a while loop runs
A while statement looks like an if: the word while, a condition, a colon and an indented block. The difference
is what happens at the end of the block. Python goes back to the top and tests the condition again, and it keeps doing
so until the test comes out false. The test happens before every pass, so when the condition is false from the
start, the body never runs at all.
How a while loop runs, with continue, break and else
Text description of the diagram
The diagram is a flowchart of a while loop, read from the top.
- Python tests the condition. When it is true, the body runs.
- When the body ends, or a continue statement runs inside it, Python goes back to step 1 and tests the condition again.
- When the test comes out false, the loop's else block runs, if the loop has one, and then the code after the loop.
- A break statement in the body leaves the loop at once: Python goes straight to the code after the loop, and the else block does not run.
This program follows the Collatz rule: halve an even number, triple an odd one and add 1, and stop at 1.
# The Collatz rule: halve an even number; triple an odd number and
# add 1. Repeat until you reach 1.
n = 6
steps = 0
print(n, end="")
while n != 1:
if n % 2 == 0:
n = n // 2
else:
n = 3 * n + 1
steps += 1
print(" ->", n, end="")
print()
print("Steps:", steps)
n = 27
steps = 0
highest = n
while n != 1:
if n % 2 == 0:
n = n // 2
else:
n = 3 * n + 1
steps += 1
if n > highest:
highest = n
print("27 needs", steps, "steps and climbs as high as", highest) Output
6 -> 3 -> 10 -> 5 -> 16 -> 8 -> 4 -> 2 -> 1 Steps: 8 27 needs 111 steps and climbs as high as 9232
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 collatz.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
Each pass changes n, which is what the condition looks at, and counts one step. That is the rule for every while
loop: something in the body must move the loop towards its stopping condition. Starting from 27, the same loop
needs 111 steps and climbs to 9,232 before it falls back to 1. Whether every starting number reaches 1 has never been
proved: the claim is called the Collatz conjecture, and research papers have so far proved only weaker statements
about it. So a loop’s stopping condition is a promise you should be able to explain, not just hope for.
When a loop never stops
A loop whose condition never becomes false runs forever. The usual causes are a body that never changes the variable
the condition tests, a change in the wrong direction (n += 1 where n -= 1 was meant), and a condition that steps
over its target: with n = 1 and while n != 10:, adding 2 on each pass gives 9 and then 11, so n is never equal to
10. A comparison such as n < 10 stops whether n lands on 10 or jumps past it.
If it happens in a terminal, press Ctrl-C: Python raises KeyboardInterrupt, which ends the program with a traceback
that shows the line it was running. In the Run bar of this page, Stop ends a run at once, and a run that goes on for
too long is stopped for you.
A common mistake: one step too far
Loops that walk through a list by position are easy to get wrong at the end:
marks = [72, 85, 64]
total = 0
i = 0
while i <= len(marks):
total += marks[i]
i += 1
print("Total:", total) Output (exit status 1)
Printed as an error (standard error)
Traceback (most recent call last):
File "off_by_one.py", line 5, in <module>
total += marks[i]
~~~~~^^^
IndexError: list index out of range
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 off_by_one.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
The list has three items, at positions 0, 1 and 2, but i <= len(marks) lets the loop run a fourth time with i
equal to 3. Use i < len(marks). The for loop of the next lesson does this counting for you, which is why lists are
usually walked with for.
break and continue
Two statements change the course of a loop from inside its body:
breakleaves the loop at once. Python goes on with the first line after the loop.continueskips the rest of this pass. In awhileloop, Python goes straight back to testing the condition.
Both act on the innermost loop they are in, and both are normally written inside an if, so that they happen only in
some passes.
while True with break
When the loop can only decide whether to stop halfway through its body, write while True: and leave with break.
Asking for input until it makes sense is the classic case:
# Keep asking until the answer is a whole number from 1 to 120.
while True:
text = input("Your age: ").strip()
if not text.isdecimal():
print("Please type your age in digits.")
continue
age = int(text)
if not 1 <= age <= 120:
print("An age from 1 to 120, please.")
continue
break
print("Thank you. Your age is", age) Input (standard input)
twenty ² 0 250 34
Output
Your age: Please type your age in digits. Your age: Please type your age in digits. Your age: An age from 1 to 120, please. Your age: An age from 1 to 120, please. Your age: Thank you. Your age is 34
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 ask_age.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
The program was given five answers, shown above its output. “twenty”, “²”, “0” and “250” are rejected, each with a
message, and continue goes back to input() for the next answer; 34 passes both checks and break ends the loop.
The answers do not appear after the prompts because the program read them from standard input, not from a keyboard;
answers typed at a keyboard would have shown on the screen as they were typed.
isdecimal() is the right check before int(): it is true only for digits that int() can read, including those of
other scripts such as ३४. The similar isdigit() also accepts characters such as ², and int("²") fails with
ValueError.
continue in a counting loop
continue skips everything below it in the body, including a counter you update at the bottom. Move the counter up:
# -1 marks a reading that the sensor failed to take
readings = [21, 22, -1, 23, -1, 24]
total = 0
count = 0
i = 0
while i < len(readings):
value = readings[i]
i += 1
if value == -1:
continue
total += value
count += 1
print("Good readings:", count)
print("Average:", total / count) Output
Good readings: 4 Average: 22.5
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 skip_failed.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
Here i += 1 comes before the continue. Had it stayed at the bottom of the body, the first -1 would have sent
the loop back to the test with i unchanged, to read the same -1 forever.
The else clause of a loop
A while loop may end with an else: block. It runs when the loop stops because its condition is false, and it does
not run when the loop is left with break (or with return or an exception). That makes it a natural fit for
searches: the break means “found it”, and the else means “searched everything, found nothing”.
First try · factor_search.py
def describe(n):
divisor = 2
while divisor * divisor <= n:
if n % divisor == 0:
print(n, "=", divisor, "x", n // divisor)
break
divisor += 1
else:
print(n, "is prime")
describe(91)
describe(97)
describe(2)
describe(1) Output
91 = 7 x 13 97 is prime 2 is prime 1 is prime
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 factor_search.py
Fixed · factor_search_fixed.py
def describe(n):
if n < 2:
print(n, "is neither prime nor composite")
return
divisor = 2
while divisor * divisor <= n:
if n % divisor == 0:
print(n, "=", divisor, "x", n // divisor)
break
divisor += 1
else:
print(n, "is prime")
describe(91)
describe(97)
describe(2)
describe(1) Output
91 = 7 x 13 97 is prime 2 is prime 1 is neither prime nor composite
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 factor_search_fixed.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
For 91, the loop finds the divisor 7 and leaves with break, so the else is skipped. For 97, no divisor up to 9
works and the next one, 10, is too big (10 × 10 is more than 97), so the condition becomes false and the else
reports a prime. Stopping at divisor * divisor <= n is enough because divisors come in pairs, d and n // d,
and the smaller of each pair is never above the square root; a later lesson of this module works that out.
The first try gets 1 wrong. For both 2 and 1, divisor * divisor <= n is false from the start, so the body never runs
and the else runs at once. That is right for 2 and wrong for 1, which is not a prime. Remember this edge case: an
else runs even when the loop did no passes at all. The fixed version deals with numbers below 2 before the loop, with
a guard clause.
Version note
New in Python 3.14: a break or continue that would leave a finally block (part of the try statement, covered
with exceptions later in this track) makes Python print SyntaxWarning: 'break' in a 'finally' block or the same for
continue (PEP 765). Such code can silently swallow an error, so move the break out of the finally block.
Key takeaways
- A
whileloop tests its condition before every pass; if it is false at the start, the body never runs. - Something in the body must move the loop towards its stopping condition; prefer
<over!=for counters, and press Ctrl-C to stop a runaway loop in a terminal. breakleaves the innermost loop;continuegoes back to the test. Update counters before anycontinue.while True:with abreakin the middle suits input that must be checked before the loop can decide.- A loop’s
elseblock runs only when the loop ends withoutbreak, also when the body never ran: ideal for searches.
Exercise
Exercise · Easy · Python
Reverse the digits of a number with arithmetic
Write two functions that work on the digits of a whole number with arithmetic alone, inside a while loop. Do not turn the number into text: no str(), no f-strings and no slicing (a sample test checks your file for them).
reverse_digits(n)returnsnwith its digits in reverse order:reverse_digits(1234)is4321. Zeros at the end ofndisappear, soreverse_digits(1200)is21; the sign stays, soreverse_digits(-56)is-65; andreverse_digits(0)is0.is_palindrome_number(n)returnsTruewhennreads the same in both directions, such as121,7and0, andFalseotherwise, such as for10and123. Negative numbers are never palindromes, because of the minus sign.
For a positive n, n % 10 is its last digit and n // 10 is n without its last digit.
Starter code · digits.py
def reverse_digits(n):
"""Return n with its digits in reverse order; the sign stays."""
result = 0
return result
def is_palindrome_number(n):
"""Return True when n reads the same both ways (never if n < 0)."""
return False The sample tests · test_digits.py
import ast
from pathlib import Path
from digits import is_palindrome_number, reverse_digits
def test_reverse():
"""reverses the digits of positive numbers"""
assert reverse_digits(1234) == 4321
assert reverse_digits(7) == 7
assert reverse_digits(90817) == 71809
def test_reverse_zeros():
"""drops the zeros at the end, and 0 stays 0"""
assert reverse_digits(1200) == 21
assert reverse_digits(10) == 1
assert reverse_digits(0) == 0
def test_reverse_negative():
"""keeps the minus sign"""
assert reverse_digits(-56) == -65
assert reverse_digits(-1000) == -1
def test_palindromes():
"""recognises numbers that read the same in both directions"""
assert is_palindrome_number(121) is True
assert is_palindrome_number(4884) is True
assert is_palindrome_number(7) is True
assert is_palindrome_number(0) is True
def test_not_palindromes():
"""rejects other numbers, negative ones included"""
assert is_palindrome_number(10) is False
assert is_palindrome_number(123) is False
assert is_palindrome_number(-121) is False
BANNED = ("str", "repr", "format")
def test_arithmetic_only():
"""uses a while loop and no str(), f-strings or slicing"""
path = Path(__file__).with_name("digits.py")
nodes = list(ast.walk(ast.parse(path.read_text(encoding="utf-8"))))
assert any(isinstance(n, ast.While) for n in nodes), "use a while loop"
for node in nodes:
if isinstance(node, ast.Call) and isinstance(node.func, ast.Name):
assert node.func.id not in BANNED, f"no {node.func.id}()"
assert not isinstance(node, ast.JoinedStr), "no f-strings"
assert not isinstance(node, ast.Slice), "no slicing" A hint
Start with result = 0. While n is more than 0, take its last digit with n % 10, add it to the end of the result with result * 10 + digit, and remove it from n with n // 10. For a negative number, reverse -n (which is positive) and put the minus sign back at the end.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
Check yourself
5 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.
References
- The Python Language Reference: The while statement (Python Software Foundation)
- The Python Tutorial: break and continue Statements (Python Software Foundation)
- The Python Tutorial: else Clauses on Loops (Python Software Foundation)
- Built-in Exceptions: KeyboardInterrupt (Python Software Foundation)
- Built-in Types: str.isdecimal (Python Software Foundation)
- Built-in Types: str.isdigit (Python Software Foundation)
- Built-in Functions: int() (Python Software Foundation)
- PEP 765: Disallow return/break/continue that exit a finally block (Python Software Foundation)
- What's New In Python 3.14: PEP 765, control flow in finally blocks (Python Software Foundation)
- Almost all orbits of the Collatz map attain almost bounded values (arXiv (Terence Tao))
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress