Chapter 4 Quiz: Control Flow — Making Decisions in Your Programs
Instructions: Answer all questions. For multiple-choice questions, select the best answer. For short-answer and applied questions, write your response in the space provided (or in a separate document). The answer key is at the end.
Total questions: 18 Suggested time: 35 minutes
Part A: Multiple Choice (Questions 1–8)
Select the one best answer.
Question 1
Which of the following correctly describes Python's elif keyword?
A) It runs only if all previous if conditions were False and its own condition is True
B) It runs immediately after the if block, regardless of whether the if condition was True
C) It is shorthand for a nested if inside an else
D) It can only appear once per if statement
Question 2
What is the output of the following code?
revenue = 45_000
tier = "Silver"
if revenue >= 50_000 and tier == "Gold":
label = "Premium"
elif revenue >= 50_000 or tier == "Gold":
label = "Qualified"
elif tier == "Silver":
label = "Standard"
else:
label = "Review"
print(label)
A) Premium
B) Qualified
C) Standard
D) Review
Question 3
Which of the following values is truthy in Python?
A) None
B) 0
C) []
D) "False"
Question 4
What does short-circuit evaluation mean in the context of and?
A) Python evaluates both sides of the and expression and returns the faster one
B) Python stops evaluating as soon as the left side is False, because the overall result cannot be True
C) Python raises an error if the first expression fails
D) Python converts both expressions to strings before comparing them
Question 5
Consider this code:
customer = None
name = customer and customer.name
What is the value of name after this line runs?
A) It raises an AttributeError
B) None
C) ""
D) True
Question 6
Which statement about match/case (Python 3.10+) is true?
A) match/case can only compare integer values
B) match/case always requires a case _: wildcard clause
C) case "A" | "B": matches if the value is either "A" or "B"
D) match/case is faster than if/elif for all use cases
Question 7
What is the output of this code?
balance = 0
if not balance:
print("No balance on file")
else:
print(f"Balance: ${balance:.2f}")
A) Balance: $0.00
B) No balance on file
C) SyntaxError
D) No output — neither branch runs
Question 8
Which of the following is the correct Python ternary expression to assign "Active" if is_enrolled is True, and "Inactive" otherwise?
A) status = "Active" else "Inactive" if is_enrolled
B) status = if is_enrolled: "Active" else "Inactive"
C) status = "Active" if is_enrolled else "Inactive"
D) status = is_enrolled ? "Active" : "Inactive"
Part B: True / False (Questions 9–12)
Write True or False. If False, write one sentence explaining why.
Question 9
A Python if statement must always be paired with an else clause.
Question 10
The expression "" or "default" evaluates to "default" because the empty string is falsy.
Question 11
In a chain of if/elif/elif/else, Python evaluates all branches and runs every one whose condition is True.
Question 12
The guard-clause pattern (exiting early from a function when inputs are invalid) is equivalent in behavior to deeply nesting conditions, but is generally considered more readable.
Part C: Short Answer (Questions 13–15)
Write 2–4 sentences.
Question 13
Explain the difference between using if value is None and if not value to check for a missing value. Give one example where they produce different behavior.
Question 14
Marcus is reviewing Priya's code and suggests she replace the following if/elif chain with a dictionary lookup. Describe when a dictionary lookup is preferable to if/elif, and when it is not appropriate.
if region == "Northeast":
rate = 0.08
elif region == "Southeast":
rate = 0.07
elif region == "Midwest":
rate = 0.075
elif region == "West":
rate = 0.09
else:
rate = 0.085
Question 15
What is the "arrow of doom," and why does it make code harder to maintain? Give one technique for avoiding it.
Part D: Applied / Code Analysis (Questions 16–18)
Question 16 — Spot the Error
This function is supposed to apply a senior discount to customers aged 65 or older, or a student discount to customers aged 25 or younger (but not both — if somehow a customer qualifies for both, senior takes priority).
Identify the bug and write the corrected version.
def apply_demographic_discount(age, base_price):
if age >= 65 or age <= 25:
if age >= 65:
discount = 0.15
if age <= 25:
discount = 0.10
return base_price * (1 - discount)
else:
return base_price
Question 17 — Write the Code
Sandra needs a function that categorizes a customer's payment history for her monthly report. Write a function categorize_payment_history(on_time_payments, late_payments, missed_payments) that returns one of four categories:
"Excellent": 0 late, 0 missed"Good": 1-2 late, 0 missed"Fair": 3-5 late, or 1 missed"Poor": more than 5 late, or more than 1 missed
Make sure your conditions handle the priority correctly — if a customer qualifies for "Fair" and "Poor", they should receive "Poor."
Question 18 — Match/Case Rewrite
Rewrite the following if/elif/else block using match/case. The behavior must be identical.
def get_support_tier_label(plan_code):
if plan_code == "S":
label = "Starter — Email support only"
elif plan_code == "P":
label = "Professional — Email + Chat"
elif plan_code == "B":
label = "Business — Priority phone"
elif plan_code == "E":
label = "Enterprise — Dedicated manager"
elif plan_code == "F":
label = "Free — Community forum only"
else:
label = f"Unknown plan code: {plan_code}"
return label
Answer Key
Part A: Multiple Choice
Q1: A
elif runs its block only if all previous conditions (the if and any earlier elif clauses) evaluated to False, and its own condition evaluates to True. It is not shorthand for a nested if/else, and multiple elif clauses are allowed.
Q2: C — Standard
- revenue >= 50_000 and tier == "Gold" → 45,000 is not >= 50,000, so the whole and is False. Skip.
- revenue >= 50_000 or tier == "Gold" → Neither is true (revenue is 45,000; tier is "Silver"). Skip.
- tier == "Silver" → True. Print "Standard".
Q3: D — "False"
"False" is a non-empty string, which is truthy. None, 0, and [] are all falsy values.
Q4: B
Short-circuit evaluation with and means Python stops as soon as it encounters a False value, because no matter what the right side evaluates to, the overall result of False and anything is False.
Q5: B — None
customer is None, which is falsy. The and operator short-circuits: since the left side (customer) is falsy, Python returns it immediately without evaluating customer.name. So name is assigned None. No error is raised.
Q6: C
case "A" | "B": uses the pipe operator to match either value — equivalent to elif value == "A" or value == "B". The wildcard case _: is optional (though recommended). match/case works with strings, integers, and other types. Performance differences vs if/elif are implementation-specific and generally not the reason to choose one over the other.
Q7: B — No balance on file
balance is 0, which is falsy. not 0 is True, so the first branch runs.
Q8: C
The Python ternary expression syntax is: value_if_true if condition else value_if_false. Options A and B are syntactically invalid. Option D is JavaScript/C syntax (the ternary operator ?: does not exist in Python).
Part B: True / False
Q9: False
An if statement is complete on its own. The else clause is optional. An if without an else simply does nothing when its condition is False.
Q10: True
"" is an empty string, which is falsy. The or operator returns the right side when the left side is falsy. Therefore "" or "default" evaluates to "default".
Q11: False
Python evaluates if/elif/elif/else chains from top to bottom and stops at the first condition that is True. Only one branch runs — the first matching one. The remaining branches are skipped.
Q12: True Both produce identical program behavior (the same branches run under the same conditions). Guard clauses are considered more readable because they make the "happy path" obvious, reduce nesting, and allow each exit condition to be understood independently.
Part C: Short Answer
Q13 — Model Answer
if value is None checks specifically and exclusively for the value None. if not value checks for any falsy value, which includes None but also 0, "", [], False, and others. They produce different behavior when the value is 0 or an empty string that represents a legitimate state rather than a missing value. For example, if discount_rate = 0.0 represents "intentionally no discount," then if not discount_rate would incorrectly treat it as "rate not set," while if discount_rate is None would correctly recognize it as a valid rate.
Q14 — Model Answer
A dictionary lookup is preferable when you are mapping a single variable (like region) to a corresponding value (like rate) with no additional logic in each branch — it is more concise and easy to extend by adding entries. It is not appropriate when the branches have different logic rather than just different values (for example, if the Northeast branch also needs to log a message, call a function, or apply additional conditions). Dictionary lookups also require knowing all valid keys upfront; if/elif handles unknown values gracefully with an else clause, while a dictionary .get() needs a default specified.
Q15 — Model Answer
The "arrow of doom" (or "pyramid of doom") is a pattern where multiple nested if statements create deeply indented code shaped like an arrowhead pointing rightward. It makes code harder to maintain because a reader must track several simultaneous conditions to understand any single branch, adding new rules requires figuring out which nesting level they belong in, and testing requires constructing very specific inputs to reach deeply nested branches. One technique for avoiding it is the guard-clause pattern: check for disqualifying conditions at the top of the function and return early, so the main logic runs at the end with minimal nesting.
Part D: Applied / Code Analysis
Q16 — Bug and Fix
The bug: when age is 65 or older, the first inner if sets discount = 0.15. But Python then evaluates the second if age <= 25: — and since 65 <= 25 is False, nothing happens. The bug occurs for customers aged 25 or younger: the first if is skipped (correct), but the second if also uses a plain if instead of elif. If age is 25, Python sets discount = 0.10 correctly. However, the structure is fragile: there is no elif, so theoretically (in a different scenario) both could fire. The more serious implied bug is that the problem says "senior takes priority" but both are plain if statements — if somehow a person could be both, the last one to run would win (the student discount), not the first.
Corrected version:
def apply_demographic_discount(age, base_price):
if age >= 65:
discount = 0.15
elif age <= 25:
discount = 0.10
else:
discount = 0.00
return base_price * (1 - discount)
Using elif ensures that once the age >= 65 condition matches, the age <= 25 condition is never evaluated — senior discount takes priority, as intended.
Q17 — Model Answer
def categorize_payment_history(on_time_payments, late_payments, missed_payments):
"""
Categorize a customer's payment history.
More severe categories checked first so they take priority.
"""
# "Poor" must be checked before "Fair" — it is a superset of some Fair conditions
if late_payments > 5 or missed_payments > 1:
return "Poor"
elif late_payments >= 3 or missed_payments == 1:
return "Fair"
elif late_payments <= 2 and missed_payments == 0:
if late_payments == 0:
return "Excellent"
else:
return "Good"
else:
return "Excellent"
Alternative clean version:
def categorize_payment_history(on_time_payments, late_payments, missed_payments):
if late_payments > 5 or missed_payments > 1:
return "Poor"
if late_payments >= 3 or missed_payments == 1:
return "Fair"
if late_payments >= 1:
return "Good"
return "Excellent"
Q18 — Model Answer
def get_support_tier_label(plan_code):
match plan_code:
case "S":
label = "Starter — Email support only"
case "P":
label = "Professional — Email + Chat"
case "B":
label = "Business — Priority phone"
case "E":
label = "Enterprise — Dedicated manager"
case "F":
label = "Free — Community forum only"
case _:
label = f"Unknown plan code: {plan_code}"
return label
Note: In this case, since each branch only assigns label and no other work is done, an alternative approach using a dictionary is also valid:
def get_support_tier_label(plan_code):
labels = {
"S": "Starter — Email support only",
"P": "Professional — Email + Chat",
"B": "Business — Priority phone",
"E": "Enterprise — Dedicated manager",
"F": "Free — Community forum only",
}
return labels.get(plan_code, f"Unknown plan code: {plan_code}")
Both implementations are correct. match/case is preferred when each branch might eventually grow to have more complex logic.