LEARN PYTHON | 02 DECISIONS AND LOOPS
Build it. Test it. Explain it.
Put comparisons, decisions, loops and debugging to work in small useful programs. Work independently before opening a hint, then compare your tested solution with one possible approach.
YOUR CHALLENGE ROUTE
Complete these tasks in your Python editor. The page is a challenge guide, not a general Python runner. Use fictional details where helpful. Work in order or choose a task matching an area you need to practise.
The solutions use the comparisons, decisions, loops and targeted input handling taught in Section 2. Earlier tasks state their numeric-text assumptions; later tasks practise controlled rejection. Record each program’s limits when testing.
TRACK YOUR TESTED SOLUTIONS
Tick a task only after you have run it, checked at least two relevant cases and explained one line. These are self-recorded checks, not an automatic assessment of your source code.
Progress lasts while this page is open. Save your actual Python files in your project folder.
01 | FOUNDATION
TEST A VALID SCORE
Create a score checker that reports whether an INTEGER is between zero and one hundred inclusive. First print the condition results, rather than immediately hiding them in a decision.
YOUR REQUIREMENTS
- Convert suitable input using int().
- Store the lower comparison and upper comparison.
- Combine them with and.
- Print a labelled validity result.
- Test both endpoints and the immediately outside values.
Hint: where to begin
Inside a range requires both limits. Compare score >= 0 and score <= 100; or between these inside comparisons would accept outside values.
TEST YOUR PROGRAM
| Test input or action | Expected result |
|---|---|
| -1 | Validity False |
| 0 | Validity True |
| 100 | Validity True |
| 101 | Validity False |
Reveal one possible solution and explanation
score = int(input("Whole-number score: "))
lower_ok = score >= 0
upper_ok = score <= 100
valid = lower_ok and upper_ok
print("Lower limit:", lower_ok)
print("Upper limit:", upper_ok)
print("Valid score:", valid)The two comparisons establish separate limits. Both must hold for a valid score. This first version assumes integer text; a range condition does not catch a conversion failure occurring before it.
A different implementation can also be correct if it meets the requirements and tests. Compare meaning and behaviour, rather than only copying the source.
Extension: Print an outside-range Boolean using or and verify that it is the opposite result for these ordinary integers.
02 | REPAIR LOGIC
REPAIR THE EITHER-RESPONSE RULE
A response checker accepts no when it should accept only yes or y. Repair the condition and explain why the original was wrong.
YOUR REQUIREMENTS
- Ask for text and strip surrounding whitespace.
- Compare the response explicitly with both accepted strings.
- Report whether the response is accepted.
- Test yes, y and no.
SOURCE TO REPAIR
response = input("Type yes or y: ")
accepted = response == "yes" or "y"
print("Accepted:", accepted)Hint: where to begin
The non-empty string "y" is truthy by itself. It does not compare the response with y.
TEST YOUR PROGRAM
| Test input or action | Expected result |
|---|---|
| yes | True |
| y | True |
| no | False |
| Blank response | False |
Reveal one possible solution and explanation
response = input("Type yes or y: ").strip()
accepted = (response == "yes") or (response == "y")
print("Accepted:", accepted)Each operand is now a comparison. The original response == "yes" or "y" evaluates a truthy second operand when the first comparison is false, so it does not implement the intended two-response test.
A different implementation can also be correct if it meets the requirements and tests. Compare meaning and behaviour, rather than only copying the source.
Extension: Add lower() to make the check case-insensitive. Explain that it returns lowercase text rather than modifying the original string automatically.
03 | FOUNDATION
CLASSIFY A NUMBER
Write a program that reports positive, negative or zero, then independently reports whether the number is even.
YOUR REQUIREMENTS
- Receive an INTEGER.
- Use one if/elif/else chain for the three exclusive categories.
- Use a separate if for evenness.
- Label the messages clearly.
Hint: where to begin
Modulo two has remainder zero for even integers, including zero and negative even values. The independent evenness notice can apply alongside a sign category.
TEST YOUR PROGRAM
| Test input or action | Expected result |
|---|---|
| -2 | Negative and Even |
| 0 | Zero and Even |
| 3 | Positive only |
Reveal one possible solution and explanation
number = int(input("Whole number: "))
if number > 0:
print("Positive")
elif number < 0:
print("Negative")
else:
print("Zero")
if number % 2 == 0:
print("Even")Sign categories are exclusive, so one chain is appropriate. Evenness is a separate property. An independent if allows a positive or negative number to receive an evenness notice without replacing its sign result.
A different implementation can also be correct if it meets the requirements and tests. Compare meaning and behaviour, rather than only copying the source.
Extension: Add a separate odd notice and test a negative odd integer.
04 | REPAIR SELECTION
REPAIR THE SCORE BANDS
The broken chain below never awards Distinction to valid scores of eighty or more. Correct it and also reject scores outside zero to one hundred.
YOUR REQUIREMENTS
- Validate the range before classifying.
- Test the highest band first.
- Return exactly one category message.
- Use boundary tests for both category changes.
SOURCE TO REPAIR
score = 85
if score >= 50:
print("Pass")
elif score >= 80:
print("Distinction")
else:
print("Not a pass")Hint: where to begin
In a chain, the first matching branch wins. A later mathematically true condition may be not tested.
TEST YOUR PROGRAM
| Test input or action | Expected result |
|---|---|
| 49; 50 | Not a pass; Pass |
| 79; 80 | Pass; Distinction |
| -1; 101 | Invalid score for both |
Reveal one possible solution and explanation
score = int(input("Score from 0 to 100: "))
if score < 0 or score > 100:
print("Invalid score")
elif score >= 80:
print("Distinction")
elif score >= 50:
print("Pass")
else:
print("Not a pass")The range rejection prevents invalid values receiving ordinary bands. Testing >= 80 before >= 50 leaves the lower threshold for values not already in the distinction route. Only one chain branch runs.
A different implementation can also be correct if it meets the requirements and tests. Compare meaning and behaviour, rather than only copying the source.
Extension: Use the debugger or diagnostic prints to record which conditions are not tested for score 85.
05 | COUNTED REPETITION
MAKE A COUNTDOWN AND TABLE
Use range to create a countdown, then a multiplication table. Explain the supplied values rather than only copying a header.
YOUR REQUIREMENTS
- Count from five down to one with a negative step.
- Print Launch after the countdown body.
- Print a chosen number’s table from multiplier one through ten.
- Read the chosen base once before its table loop.
Hint: where to begin
Both range stops are excluded. The countdown stops before zero, and the table stops before eleven.
TEST YOUR PROGRAM
| Test input or action | Expected result |
|---|---|
| Countdown | 5, 4, 3, 2, 1, then Launch! |
| Base 3 | First product 3; final product 30; ten rows |
| Base 0 | Ten products equal zero |
Reveal one possible solution and explanation
for number in range(5, 0, -1):
print(number)
print("Launch!")
base = int(input("Table number: "))
for multiplier in range(1, 11):
print(f"{base} x {multiplier} = {base * multiplier}")for assigns each supplied range value to its loop variable. The indented body repeats; the launch statement is outside the body and runs once after it. Excluding the stop gives the intended endpoints.
A different implementation can also be correct if it meets the requirements and tests. Compare meaning and behaviour, rather than only copying the source.
Extension: Ask for the countdown start. State what happens for zero and a negative start under your chosen requirement.
06 | ACCUMULATION
TOTAL THE EVEN VALUES
Calculate the sum and count of even integers from one through an inclusive positive limit. Report a mean only when at least one qualifies.
YOUR REQUIREMENTS
- Initialise total and count once before the loop.
- Use a range that includes the requested limit.
- Add a value only when its remainder modulo two is zero.
- Count qualifying values separately.
- Guard the mean against count zero.
Hint: where to begin
A total adds the amount; a count adds one. A limit of one contains no positive even values.
TEST YOUR PROGRAM
| Test input or action | Expected result |
|---|---|
| Limit 6 | Total 12; count 3; mean 4.0 |
| Limit 5 | Total 6; count 2; mean 3.0 |
| Limit 1 | Total 0; count 0; no mean calculation |
Reveal one possible solution and explanation
limit = int(input("Positive whole-number limit: "))
total = 0
count = 0
for number in range(1, limit + 1):
if number % 2 == 0:
total += number
count += 1
print("Even total:", total)
print("Even count:", count)
if count > 0:
print("Even mean:", total / count)
else:
print("No qualifying values")limit + 1 compensates for the excluded stop. The condition filters additions without changing the range visits. The guard prevents division by zero when no positive even value qualifies.
A different implementation can also be correct if it meets the requirements and tests. Compare meaning and behaviour, rather than only copying the source.
Extension: Add temporary partial-total prints, predict them for limit six, then remove the diagnostic output.
07 | CONDITION-CONTROLLED LOOP
RETRY A SCORE UNTIL VALID
Ask for an INTEGER score and keep retrying until it lies from zero to one hundred inclusive. This version assumes every response is integer text.
YOUR REQUIREMENTS
- Receive a value before the first condition test.
- Repeat while either outside-range condition is true.
- Read a new response during every retry.
- Print the accepted score after the loop.
Hint: where to begin
Inside uses and; outside uses or. A valid first response should need no retry-body execution.
TEST YOUR PROGRAM
| Test input or action | Expected result |
|---|---|
| 50 | Immediate acceptance with no retry message |
| -1, then 100 | One retry, then accepted 100 |
| 101, then 0 | One retry, then accepted 0 |
Reveal one possible solution and explanation
score = int(input("Score from 0 to 100: "))
while score < 0 or score > 100:
print("Outside the allowed range")
score = int(input("Try again: "))
print("Accepted score:", score)while tests before its body. The initial valid response skips the body; an invalid response enters it. The new assignment ensures the next test examines the new value instead of repeatedly rejecting the same one.
A different implementation can also be correct if it meets the requirements and tests. Compare meaning and behaviour, rather than only copying the source.
Extension: After mastering the core, add targeted ValueError handling so unsuitable text can also be retried.
08 | SENTINEL AND VALIDATION
TOTAL UNTIL THE FINISH MARKER
Total non-negative INTEGER counts until -1 is entered. Ignore other negative values with feedback rather than adding them. Assume integer text input.
YOUR REQUIREMENTS
- Use -1 as the sentinel.
- Exit with break before adding the sentinel.
- Use continue to skip other negative values.
- Read a new value at the beginning of every iteration.
- Print the final total once.
Hint: where to begin
A while True loop needs a reachable exit. Reading at the top means continue still leads to another input rather than freezing the state.
TEST YOUR PROGRAM
| Test input or action | Expected result |
|---|---|
| 4, 7, -1 | Total 11 |
| -2, 5, -1 | Reject -2; total 5 |
| -1 | Total 0 with no ordinary values |
Reveal one possible solution and explanation
total = 0
while True:
value = int(input("Non-negative count, or -1 to finish: "))
if value == -1:
break
if value < 0:
print("That negative value is not accepted")
continue
total += value
print("Total:", total)The sentinel is a control signal, not data. Its check precedes the negative-value rejection. continue skips the addition for an unacceptable negative count; the next iteration reads a fresh value.
A different implementation can also be correct if it meets the requirements and tests. Compare meaning and behaviour, rather than only copying the source.
Extension: Count accepted values separately and output their mean only when count is positive.
09 | CONTROL-FLOW INVESTIGATION
TRACE BREAK AND CONTINUE
Trace the sample below before running it. Explain why three and five do not print, and why the loop finishes without printing six.
YOUR REQUIREMENTS
- Record count after each update.
- Record which if tests are reached.
- List printed values in order.
- Distinguish leaving the iteration from leaving the loop.
Hint: where to begin
The update occurs before continue. break at five stops the loop before its normal condition reaches six.
TEST YOUR PROGRAM
| Test input or action | Expected result |
|---|---|
| Original program | Outputs 1, 2, 4, then Finished |
| Change skipped value to 2 | Outputs 1, 3, 4, then Finished |
| Explain count at exit | count is 5; break stops before print |
Reveal one possible solution and explanation
count = 0
while count < 6:
count += 1
if count == 3:
continue
if count == 5:
break
print(count)
print("Finished")continue at three skips the second test and print for that iteration. break at five exits the loop entirely. The initial update keeps progress even when the remaining body is skipped.
A different implementation can also be correct if it meets the requirements and tests. Compare meaning and behaviour, rather than only copying the source.
Extension: Trace what would happen if the update were moved below continue. Do not run an unbounded broken version to prove the problem.
10 | DEBUG AND TEST
RUN A SMALL DEBUGGING CLINIC
Repair the repeated-total reset and then add controlled integer input handling to a separate short program. Explain the fault and the handler’s responsibility.
YOUR REQUIREMENTS
- Initialise total before its for loop.
- Predict total for one through three.
- Use except ValueError around an integer conversion.
- Test successful and failed conversion separately.
- Do not claim conversion handling proves range validity.
SOURCE TO REPAIR
for number in range(1, 4):
total = 0
total += number
print(total)Hint: where to begin
A repeated reset erases earlier contributions. Put only the potentially failing conversion inside try.
TEST YOUR PROGRAM
| Test input or action | Expected result |
|---|---|
| Repaired total | Final total 6 |
| Input 4 | Converted count: 4 |
| Input four | Controlled conversion message, not a successful count |
| Input -2 | Conversion succeeds; a range rule would be separate |
Reveal one possible solution and explanation
total = 0
for number in range(1, 4):
total += number
print("Total:", total)
text = input("Whole-number count: ")
try:
count = int(text)
except ValueError:
print("That response is not a whole number")
else:
print("Converted count:", count)total = 0 belongs before repetition to preserve earlier additions. The ValueError handler responds to unsuitable integer text. A successfully converted negative number may still be unsuitable for the application, so validation remains separate.
A different implementation can also be correct if it meets the requirements and tests. Compare meaning and behaviour, rather than only copying the source.
Extension: Use a labelled diagnostic print after each addition, compare with a dry run and remove it after verification.
11 | CAPSTONE
BUILD THE GUESSING GAME
Build the complete Section 2 game with secret twelve for testing before using a random target.
YOUR REQUIREMENTS
- Allow five accepted in-range INTEGER guesses from 1 to 20.
- Give low/high feedback and record success.
- Retry unsuitable text and out-of-range numbers without charging an attempt.
- Accept quit as a voluntary exit.
- Distinguish quit, win and exhausted guesses after the loop.
- Test a correct fifth guess before switching to randint.
Hint: where to begin
Choose the secret and initialise flags before the loop. Update attempts only after successful conversion and range checks.
TEST YOUR PROGRAM
| Test input or action | Expected result |
|---|---|
| 7, 16, 12 | Low, high, win with 3 accepted guesses |
| hello, 25, 12 | Two rejection messages; win with 1 accepted guess |
| 1, 2, 3, 4, 12 | Win on guess 5, not a loss |
| 1, 2, 3, 4, 5 | Loss reveals secret 12 |
| quit | Voluntary exit after 0 accepted guesses |
Reveal one possible solution and explanation
# Number guessing game: fixed secret for reproducible testing
MIN_NUMBER = 1
MAX_NUMBER = 20
MAX_ATTEMPTS = 5
secret = 12
attempts = 0
won = False
quit_game = False
print("Guess the secret number")
print(f"Range: {MIN_NUMBER} to {MAX_NUMBER}; accepted guesses: {MAX_ATTEMPTS}")
while attempts < MAX_ATTEMPTS and not won:
text = input(f"Guess {MIN_NUMBER} to {MAX_NUMBER}, or quit: ").strip()
if text == "quit":
quit_game = True
break
try:
guess = int(text)
except ValueError:
print("Enter a whole number or the command quit")
continue
if guess < MIN_NUMBER or guess > MAX_NUMBER:
print("That number is outside the allowed range")
continue
attempts += 1
if guess < secret:
print("Too low")
elif guess > secret:
print("Too high")
else:
won = True
if quit_game:
print(f"Game ended after {attempts} accepted guesses.")
elif won:
print(f"Correct! You used {attempts} accepted guesses.")
else:
print(f"No guesses left. The secret was {secret}.")The state records what happened, rather than treating every loop exit as a loss. continue prevents rejected responses reaching the counter; break handles quit; the while condition stops after success or the accepted-attempt limit.
A different implementation can also be correct if it meets the requirements and tests. Compare meaning and behaviour, rather than only copying the source.
Extension: Add a remaining-attempt message after each wrong accepted guess. Rerun all five route tests.
12 | REFLECT AND EXTEND
PERSONALISE, HAND OVER AND REGRESSION TEST
Improve the game without weakening its rules. Give a partner a short user guide and ask them to test it without coaching.
YOUR REQUIREMENTS
- Choose one improvement: clearer remaining guesses or case-insensitive quit.
- Make one change at a time.
- Rerun win, last-attempt win, loss, invalid-response and quit tests.
- Keep fixed-secret testing separate from random play.
- Write a short explanation of one loop-control decision.
Hint: where to begin
To make the command case-insensitive, compare text.lower() with "quit". Keep integer conversion on the original stripped response.
TEST YOUR PROGRAM
| Test input or action | Expected result |
|---|---|
| QUIT | Voluntary exit after 0 accepted guesses |
| quit with surrounding spaces | Voluntary exit after 0 accepted guesses |
| 12 | Win with 1 accepted guess |
| hello, 12 | One rejection; win with 1 accepted guess |
Reveal one possible solution and explanation
# Number guessing game: fixed secret for reproducible testing
MIN_NUMBER = 1
MAX_NUMBER = 20
MAX_ATTEMPTS = 5
secret = 12
attempts = 0
won = False
quit_game = False
print("Guess the secret number")
print(f"Range: {MIN_NUMBER} to {MAX_NUMBER}; accepted guesses: {MAX_ATTEMPTS}")
while attempts < MAX_ATTEMPTS and not won:
text = input(f"Guess {MIN_NUMBER} to {MAX_NUMBER}, or quit: ").strip()
if text.lower() == "quit":
quit_game = True
break
try:
guess = int(text)
except ValueError:
print("Enter a whole number or the command quit")
continue
if guess < MIN_NUMBER or guess > MAX_NUMBER:
print("That number is outside the allowed range")
continue
attempts += 1
if guess < secret:
print("Too low")
elif guess > secret:
print("Too high")
else:
won = True
if quit_game:
print(f"Game ended after {attempts} accepted guesses.")
elif won:
print(f"Correct! You used {attempts} accepted guesses.")
else:
print(f"No guesses left. The secret was {secret}.")lower returns lowercase text for the command comparison. The existing strip removes surrounding whitespace. The numeric path, validation order and accepted-attempt count remain unchanged. Rechecking earlier routes provides evidence that the extension did not damage them.
A different implementation can also be correct if it meets the requirements and tests. Compare meaning and behaviour, rather than only copying the source.
Extension: Plan replay as a later extension: identify which state must be reset each round before attempting to implement it.
KEEP A SMALL TEST RECORD
Record input, expected behaviour, actual behaviour and your conclusion. Choose cases that distinguish the fault rather than only ordinary values.
| Program | Input | Expected |
|---|---|---|
| Score bands | 80 | Distinction only |
| Even total | Limit 1 | Total 0, count 0, no mean |
| Guessing game | hello, 25, 12 with secret 12 | Two rejections, win with one accepted guess |
An unhandled exception is not the same as intended controlled rejection. For a known limitation, record the failure and the broken assumption. After a correction, rerun earlier passing cases.
READY FOR THE NEXT SECTION?
You are ready to move on when you can explain Boolean conditions, choose a selection route, trace a for or while loop, handle a known conversion failure and test the game’s different outcomes.
If one skill is uncertain, revisit its lesson and change a worked example before repeating the challenge. The Section 2 workbook is coming soon.
Next: strings, indexing and methods Python course navigation