◀ Course contents Part 2 · Module 2-03

while Loops

Repeating when you do not know how many times

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:

attempt = 0
delay = 1
while attempt < MAX_ATTEMPTS:
    attempt += 1
    if call_succeeded:
        break
    total_wait += delay
    delay *= 2          # 1, 2, 4, 8 — exponential backoff

Quick check

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
Python
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
    Python
    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
      Python
      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
        Python
        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:

          Digits only, please.
          Digits only, please.
          Quantity accepted: 12
          Attempts: 3

          Typed in for you when this runs: twelve ⏎ 12.5 ⏎ 12

          your_code.py
          Python
          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
            Python
            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
              Python
              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
                Python
                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
                  Python
                  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
                    Python
                    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.

                    Output
                    
                          

                      Notification