Solutions 08: Control Flow¶
Overview¶
Chapter 08 exercises cover conditionals, truthiness, while and for loops, break and continue, enumerate() and zip(), the loop else clause, and building interactive programs. This solution guide explains the reasoning behind each exercise.
Notes Before Checking Solutions¶
Control flow is where programs start to feel alive — they make decisions and repeat actions. The most important habit to build here is writing loops that always terminate. An infinite loop that you did not intend is one of the most frustrating bugs to debug.
Warm-up Exercise Solutions¶
Exercise 1: Write Simple Conditionals¶
age = 18
if age >= 18:
print("You are an adult.")
score = 75
if score >= 70:
print("You passed!")
else:
print("You failed.")
grade_points = 85
if grade_points >= 90:
grade = "A"
elif grade_points >= 80:
grade = "B"
elif grade_points >= 70:
grade = "C"
else:
grade = "F"
print(f"Grade: {grade}") # B
How if/elif/else works: Python checks each condition from top to bottom and executes the first block whose condition is True. Once a match is found, the remaining conditions are skipped. The else block runs only if no condition matched.
Nested conditionals:
temperature = 25
if temperature > 20:
if temperature > 30:
print("It is hot!")
else:
print("It is warm.") # This runs
else:
print("It is cold.")
Nested conditionals work, but they can become hard to read. When possible, flatten them using and:
Exercise 2: Understand Truthiness¶
Falsy values: 0, 0.0, "", [], (), {}, set(), None, False
Everything else is truthy.
# These all print the "falsy" branch
if 0: ...
if "": ...
if []: ...
if None: ...
# These all print the "truthy" branch
if 1: ...
if "hello": ...
if [1, 2, 3]: ...
Using truthiness directly:
name = input("Enter your name (or press Enter to skip): ")
if name:
print(f"Hello, {name}!")
else:
print("You did not enter a name.")
if name: is equivalent to if name != "": but more Pythonic. An empty string is falsy, so the else branch runs when the user presses Enter without typing anything.
Exercise 3: Use while Loops¶
The loop condition is checked before each iteration. When count reaches 5, the condition count < 5 is False and the loop stops.
while True with break:
while True:
user_input = input("Enter 'quit' to exit: ")
if user_input.lower() == "quit":
break
print(f"You entered: {user_input}")
while True creates an infinite loop. break exits it immediately. This pattern is useful when the exit condition is in the middle of the loop body, not at the top.
continue:
count = 0
while count < 10:
count += 1
if count % 2 == 0:
continue # skip even numbers
print(f"Odd number: {count}")
continue skips the rest of the current iteration and jumps back to the loop condition. Here it skips the print for even numbers.
Exercise 4: Use for Loops¶
fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
print(f"I like {fruit}.")
# Loop over a string
for letter in "Python":
print(letter)
# range()
for i in range(5): # 0, 1, 2, 3, 4
print(i)
for i in range(2, 8): # 2, 3, 4, 5, 6, 7
print(i)
for i in range(0, 11, 2): # 0, 2, 4, 6, 8, 10
print(i)
range() is exclusive of the stop value. range(5) gives 0, 1, 2, 3, 4 — not 5. range(2, 8) gives 2, 3, 4, 5, 6, 7 — not 8.
Nested loops:
The inner loop runs completely for each iteration of the outer loop. For a 3×3 table, the inner loop runs 9 times total.
Practice Exercise Solutions¶
Exercise 5: Use enumerate() and zip()¶
fruits = ["apple", "banana", "cherry"]
# enumerate() — get index and value together
for index, fruit in enumerate(fruits):
print(f"{index}: {fruit}")
# 0: apple
# 1: banana
# 2: cherry
# Start counting from 1
for index, fruit in enumerate(fruits, start=1):
print(f"{index}. {fruit}")
# 1. apple
# 2. banana
# 3. cherry
Why enumerate() instead of range(len(fruits))? Both work, but enumerate() is more readable and Pythonic. It avoids the awkward fruits[i] indexing.
names = ["Alice", "Bob", "Carol"]
ages = [30, 25, 28]
# zip() — iterate over two lists in parallel
for name, age in zip(names, ages):
print(f"{name} is {age} years old.")
zip() stops at the shortest iterable. If the lists have different lengths, zip() stops when the shorter one is exhausted. Use itertools.zip_longest() if you need to handle unequal lengths.
Exercise 6: Use Loop else Clause¶
# else runs if the loop completes without break
for i in range(1, 4):
if i == 5:
print("Found 5!")
break
else:
print("5 not found in range 1-3.") # This runs
The else clause on a loop runs when the loop finishes normally (without hitting break). It does not run if break was used to exit.
Practical use — searching:
def find_item(items, target):
for item in items:
if item == target:
print(f"Found: {target}")
break
else:
print(f"{target} not found.")
find_item([1, 2, 3], 2) # Found: 2
find_item([1, 2, 3], 5) # 5 not found.
This is cleaner than using a flag variable (found = False).
Exercise 7: Build a Menu-Driven Program¶
def show_menu():
print("\n=== Menu ===")
print("1. Say hello")
print("2. Add two numbers")
print("3. Check if number is even")
print("4. Exit")
def main():
while True:
show_menu()
choice = input("Enter your choice (1-4): ")
if choice == "1":
name = input("What is your name? ")
print(f"Hello, {name}!")
elif choice == "2":
a = float(input("First number: "))
b = float(input("Second number: "))
print(f"{a} + {b} = {a + b}")
elif choice == "3":
n = int(input("Enter a number: "))
if n % 2 == 0:
print(f"{n} is even.")
else:
print(f"{n} is odd.")
elif choice == "4":
print("Goodbye!")
break
else:
print("Invalid choice. Try again.")
if __name__ == "__main__":
main()
Why compare choice to strings like "1" instead of integers? input() always returns a string. Comparing to "1" avoids a conversion step and a potential ValueError.
Why while True with break? The menu should keep showing until the user chooses to exit. The exit condition is inside the loop, not at the top.
Challenge Exercise Solutions¶
Challenge 1: Validate Input in a Loop¶
def get_positive_integer(prompt):
"""Get a positive integer from the user."""
while True:
try:
value = int(input(prompt))
if value > 0:
return value
else:
print("Please enter a positive number.")
except ValueError:
print("That is not a valid integer.")
This function combines three techniques:
1. while True to keep asking until valid input is received
2. try/except to handle non-integer input
3. A range check to ensure the value is positive
return value exits both the loop and the function at once. This is cleaner than setting a flag and breaking.
Challenge 2: Find Patterns in Numbers¶
# Find all prime numbers in a range
for n in range(2, 31):
is_prime = True
for i in range(2, int(n ** 0.5) + 1):
if n % i == 0:
is_prime = False
break
if is_prime:
print(n, end=" ")
Why check up to sqrt(n)? If n has a factor larger than its square root, it must also have a factor smaller than its square root. So checking up to sqrt(n) is sufficient. This makes the algorithm much faster for large numbers.
Why break after finding a factor? Once we know n is not prime, there is no need to check more factors. break exits the inner loop immediately.
Fibonacci sequence:
The simultaneous assignment a, b = b, a + b updates both variables at once. Python evaluates the right side completely before assigning, so a + b uses the old value of a.
Challenge 3: Build a Guessing Game with Hints¶
import random
def play_game():
secret = random.randint(1, 100)
guesses = 0
max_guesses = 7
print("I am thinking of a number between 1 and 100.")
print(f"You have {max_guesses} guesses.")
while guesses < max_guesses:
try:
guess = int(input(f"\nGuess #{guesses + 1}: "))
guesses += 1
if guess < secret:
difference = secret - guess
if difference > 50:
print("Way too low!")
else:
print("Too low.")
elif guess > secret:
difference = guess - secret
if difference > 50:
print("Way too high!")
else:
print("Too high.")
else:
print(f"Correct! You guessed it in {guesses} tries.")
return True
except ValueError:
print("Please enter a valid integer.")
guesses -= 1 # Don't count invalid input as a guess
print(f"Game over! The number was {secret}.")
return False
Why guesses -= 1 after a ValueError? We increment guesses before checking the value. If the input is invalid, we undo the increment so the user does not lose a guess for a typo.
Why return True/False? Returning a value lets the caller know whether the player won. This makes the function reusable — you could call it in a loop to play multiple rounds and track wins.
Challenge 4: Multiplication Table Generator¶
def print_multiplication_table(size):
"""Print a multiplication table."""
# Header row
print(" ", end="")
for i in range(1, size + 1):
print(f"{i:4}", end="")
print()
# Separator line
print(" " + "-" * (size * 4))
# Data rows
for i in range(1, size + 1):
print(f"{i:2} |", end="")
for j in range(1, size + 1):
print(f"{i * j:4}", end="")
print() # newline at end of each row
size = int(input("Enter table size (1-12): "))
if 1 <= size <= 12:
print_multiplication_table(size)
else:
print("Size must be between 1 and 12.")
Format specifier {i:4}: This right-aligns the number in a field 4 characters wide. All numbers in the same column will be aligned regardless of how many digits they have.
print(..., end=""): By default, print() adds a newline at the end. end="" suppresses the newline so the next print() continues on the same line.
Common Mistakes¶
Infinite loop because the loop variable is never updated. Always make sure the condition will eventually become False, or use break to exit.
Off-by-one with range(). range(n) gives 0 to n-1. range(1, n+1) gives 1 to n. Think carefully about which you need.
break only exits the innermost loop. If you have nested loops and need to exit both, use a flag variable or restructure the code.
Forgetting continue skips the rest of the current iteration. Code after continue in the same loop body does not run for that iteration.
Using = instead of == in a condition. if x = 5: is a syntax error in Python. Use if x == 5:.
What to Review Next¶
- Review the matching handbook chapter if any exercise felt difficult.
- Revisit the matching exercise set and try solving it again without looking at the solution.
- Continue with the next handbook chapter: Chapter 09 - Collections