All 24 modules are open from the start — nothing here is
locked, and nothing costs anything. Sign in so your progress, titles and
credentials stay with you, on every device you use.
Free forever, with your Google account. No password, no payment.
A for loop needs to know what it is walking over. Plenty
of real work does not: keep asking until the answer is valid, keep retrying until
the service responds, keep going until the money runs out. A while
loop repeats on a condition rather than a sequence — which is more
powerful, and the one kind of loop that can fail to stop.
Ready?
1
Every while Loop Has Three Parts
A while loop checks a condition, runs its block if the
condition is true, then checks again. It keeps doing that until the
condition is false.
count = 5 # 1. set up
while count > 0: # 2. condition
print(count)
count -= 1 # 3. move towards the exit
print("Liftoff")
All three parts are load-bearing, and each has its own failure. Forget the
setup and you get a NameError. Get the condition backwards
and the block never runs at all. Forget the update and the loop runs
forever, because nothing it does ever makes the condition false.
The condition is checked before each pass, including the
first one. A while loop whose condition starts out false runs
zero times — which is correct, and occasionally surprising.
Runs forever
while count > 0: print(count)
Nothing changes count, so the condition can never turn false.
Ends
while count > 0: print(count) count -= 1
Every pass moves the state towards the condition being false.
Quick check
count starts at 0 and the loop is while count > 0:. How many times does the block run?
2
Infinite Loops, and Why They Are Not Rare
An infinite loop is not an exotic mistake. It is what you get by default
whenever the loop's own work fails to move the state towards the exit,
and there are three ordinary ways in:
1
No update at all
The counter is never incremented, usually because the line ended up outside the block.
2
Updating the wrong thing
The condition watches one variable and the block changes another.
3
An exit that cannot be reached
while balance != 0: subtracting 3 from 10 steps straight past zero to -2 and keeps going.
That third one is worth dwelling on. != in a loop condition
demands the state land exactly on the value. >
and < catch everything past it, which is why they are the
safer default.
Nothing here can hang your browser
The lab runs your code in a worker with a ten-second limit. An infinite
loop costs you ten seconds and a click, not a lost tab — so this is the
one place where writing one deliberately, to see what it does, is free.
Quick check
balance = 10, and the loop is while balance != 0: with balance -= 3 inside. What happens?
3
The Two Patterns You Will Actually Write
The validation loop. Keep asking until the answer is
usable. This is the one that makes a command-line tool feel finished
rather than fragile:
answer = input("Age: ").strip()
while not answer.isdigit():
print("Digits only, please.")
answer = input("Age: ").strip()
age = int(answer)
Note that the read appears twice — once to get something to test, once to
try again. That duplication is what while True removes:
while True:
answer = input("Age: ").strip()
if answer.isdigit():
break
print("Digits only, please.")
while True: looks alarming and is entirely normal. The
condition is always true, so the exit is a break in the
middle — which is exactly where the decision belongs when you cannot test
anything until after you have read it.
The retry loop. Same shape, with a limit and a growing
wait, so a service that is briefly unwell gets a chance to recover and a
service that is genuinely down does not get hammered:
Why does a retry loop double the delay rather than waiting the same amount each time?
4
Which Loop Does This Job Want?
The question is not which is more powerful — a while can do
everything a for can. The question is which one states the
problem.
Use for
when you know what you are walking over: a list, a string, a range, a file. The end is guaranteed and the code says what it is doing to each item.
Use while
when you only know what stops you: valid input, a successful call, a balance reaching zero. The number of passes is not knowable up front.
A useful tell: if you find yourself writing while i < len(items):
and incrementing i by hand, you have written a
for loop the long way. Three lines of bookkeeping have
replaced one, and every one of them is a place to make a mistake.
The reverse tell is a for loop over a big range with a
break that almost always fires on the second pass. That is a
while wearing a costume.
Every while loop owes you an answer
Before you run one, be able to say what makes it stop, and be able to
point at the line that brings that about. If you cannot point at the
line, it is not there.
When there is no natural limit, add an artificial one. A retry loop with
a maximum attempt count cannot hang, whatever the network does.
Quick check
You need to read lines from a file until you hit one that says END. Which loop?
0 of 9 completed
Real Python runs right here in your browser — nothing to install, nothing
sent to a server. The interpreter downloads once the first time you press
Run, then stays cached.
01
To do
A while loop checks a condition, runs its block if the
condition holds, then checks again. Three parts, all of them required:
count = 5 # set up while count > 0: # condition print(count) count -= 1 # move towards the exit
Your task: count down from 5 to 1, one per line, then
print Liftoff after the loop has finished:
5 4 3 2 1 Liftoff
your_code.py
PythonCtrl↵ to run
Hint
while count > 0: then print(count) and count -= 1 inside it. Liftoff goes at the left margin, after the loop.
Output
02
To do
The condition is checked before the first pass, so a condition
that starts out false means the block never runs at all. No error, no
warning — just nothing.
This is correct behaviour and it is occasionally exactly what you want.
It is also what a comparison written the wrong way round looks like.
Your task: this is meant to process four queued jobs and
prints 0 instead. Fix the condition so it prints:
4
your_code.py
PythonCtrl↵ to run
Hint
Read the condition aloud: "while the number processed is greater than the queue length". That is true only once the work is done. It should be the other way round.
Output
03
To do
Run this before changing anything. It will sit there printing until the
lab stops it after ten seconds — which is free, and worth seeing once.
Nothing in the block changes count, so the condition that
started true stays true forever. Every infinite loop is this: work that
never moves the state towards the exit.
Your task: add the missing update so it prints exactly
three lines and stops:
Retrying... Retrying... Retrying...
your_code.py
PythonCtrl↵ to run
Hint
count -= 1 inside the loop, indented with the print. Without it the condition is checked forever against a number that never moves.
Output
04
To do
A for loop needs to know how many passes to make. This is the
case where you do not: you know where you want to end up, not how many
steps it takes.
Your task: each writing session adds 250 words. Starting
from zero, keep adding sessions until the total reaches at least
target, counting them as you go. Print:
Sessions: 4 Words: 1000
Work both numbers out — neither may be typed in.
your_code.py
PythonCtrl↵ to run
Hint
while words < target: then add WORDS_PER_SESSION to words and 1 to sessions inside. The condition is "not there yet", so it is < rather than >=.
Output
05
To do
This is the loop that makes a command-line tool feel finished. Read an
answer, and while it is not something you can use, say so and read
another.
answer = input("Age: ").strip() while not answer.isdigit(): print("Digits only, please.") answer = input("Age: ").strip()
Note that the read appears twice: once to have something to test, once to
try again. That duplication is real, and the next exercise removes it.
Your task: three answers are typed in for you —
twelve, 12.5 and 12. Keep asking
until one is all digits, counting the attempts, then print:
Typed in for you when this runs:twelve ⏎ 12.5 ⏎ 12
your_code.py
PythonCtrl↵ to run
Hint
while not answer.isdigit(): then the complaint, another input(), and attempts += 1 — all three inside the block.
Output
06
To do
Sometimes there is nothing to test until after you have read. Writing the
read twice works and duplicates a line;
while True with a break in the middle says it
once:
while True: line = input().strip() if line == "done": break print(line)
while True: looks alarming and is completely ordinary. The
condition is always true, so the exit is the break — and that
is the right shape whenever the decision can only be made after the read.
The value that ends the loop is called a sentinel. It is
not data, so it must not be counted or printed.
Your task: read settings until the line
done, printing each real one and counting them:
port=8080 debug=false Read 2 settings
Typed in for you when this runs:port=8080 ⏎ debug=false ⏎ done
your_code.py
PythonCtrl↵ to run
Hint
while True: read the line, break out when it is "done", and only then print it and add one to settings. The order matters — break first, or the sentinel gets counted.
Output
07
To do
Off-by-one bugs almost always live on the comparison in the condition.
while n < 5 stops after 4. while n <= 5
includes 5. Neither is wrong in general; one of them is wrong for what you
wanted.
There is no trick for this beyond checking the boundary by hand: write
down what the first pass and the last pass should be, then read the
condition against them.
Your task: this should print 1 through 5 and stops at 4.
Fix the condition, changing nothing else:
1 2 3 4 5
your_code.py
PythonCtrl↵ to run
Hint
The last number you want is 5, so the condition has to still be true when n is 5.
Output
08
To do
while balance != 0: demands the state land exactly
on zero. Take 3 away from 10 and it goes 10, 7, 4, 1, -2 — past the exit
and away, forever.
This is why > and < are the safer default
in a loop condition: they catch everything beyond the boundary rather than
one exact value. Save != for conditions where you control the
steps and know they land.
Your task: this runs forever. Run it once to see, then
change the condition so it prints each balance and then the final
overdrawn figure:
10 7 4 1 Final: -2
your_code.py
PythonCtrl↵ to run
Hint
Ask whether there is anything left rather than whether it is exactly nothing: while balance > 0.
Output
09
To do
Real loops usually have more than one way to end. This one runs out of
orders, or runs out of stock, whichever comes first — and both belong in
the condition, joined by and.
Order matters, for the reason from the operators module:
index < len(orders) has to come first, because the check
after it looks up orders[index] and would fail on an index
that does not exist.
Your task: fill orders from stock in order, stopping at
the first one you cannot fill completely. Count what you filled and report
what is left:
Filled: 2 Stock left: 30
The orders are 40, 30, 50 and 20, and there are 100 units. The third one
does not fit, and the fourth is not considered.
your_code.py
PythonCtrl↵ to run
Hint
while index < len(orders) and stock >= int(orders[index]): then take the amount off stock, add one to filled, and move index on. The length check has to be the left-hand half.
Output
The retry loop
To do
A service you depend on is flaky. Calling it once and giving up is fragile;
calling it forever is worse. The standard answer is a bounded retry loop
with an exponential backoff, and you are going to write one.
outcomes is what the service returns on each attempt, in order.
Read one per attempt.
The rules:
Never make more than MAX_ATTEMPTS calls, whatever happens
Count every call in attempts
On OK, set succeeded and stop immediately — the remaining outcomes must not be read
On FAIL, add the current delay to total_wait, then double delay
The delay starts at 1, so the waits go 1, 2, 4, 8 — quick enough to ride out
a blip, and backing off fast enough not to hammer a service that is
genuinely down.
Then print exactly four lines:
Attempts: 4 Succeeded: True Total wait: 7s Final delay: 8s
your_code.py
PythonCtrl↵ to run
Hint
while attempt < MAX_ATTEMPTS: read outcomes[attempt] into a variable, then add one to attempt. If the result is "OK", set succeeded and break. Otherwise add delay to total_wait and double delay with delay *= 2. Read the outcome before incrementing the attempt, or you skip the first one.