A community calendar needs to decide whether the given year 2024 gets a leap day. The finished program should print one exact message:
2024 is a leap year.
How can a program choose the right message instead of printing every possibility?
A leap year is divisible by 4, except a century year is not a leap year, except every 400th year is a leap year after all. That explains why 1900 is not a leap year, while 2000 and 2024 are leap years. In Python, the full test is (year % 4 == 0 and year % 100 != 0) or year % 400 == 0.
Run the single given year below.
Preview — you will build every part of this by the end of the unit.
year = 2024
is_leap_year = (year % 4 == 0 and year % 100 != 0) or year % 400 == 0
if is_leap_year:
print(f"{year} is a leap year.")
else:
print(f"{year} is not a leap year.")
Output
2024 is a leap year.Notice
The boolean test becomes True for 2024, so only the first indented message runs. We will build this payoff in three lessons. Each idea climbs the same ladder: minimal, one twist, then realistic.
You will learn
- Choose when code runs with
if,elif, andelse. - Combine yes-or-no tests with
and,or, andnot. - Put one decision inside another and order the branches so the right one runs.
Lesson 1: Choose with if
A one-way decision
An if statement runs its indented block only when its condition is True. A colon ends the condition line.
Minimal: print a message when a score reaches a target.
score = 92
if score >= 90:
print("Target reached!")
Output
Target reached!Notice
The indented line belongs to the decision. It runs because 92 >= 90 is True.
One twist: a false condition skips its block.
temperature = 65
if temperature > 80:
print("Bring water.")
print("Check complete.")
Output
Check complete.Notice
The indented reminder is skipped, but the unindented final line runs.
Realistic: use an equality test to recognize an exact score. A common broken version uses assignment inside the condition.
Read the error
score = 90
if score = 90:
print("Exact score!")
The traceback ends with SyntaxError because = saves a value; it does not ask an equality question. Use == in the condition.
score = 90
if score == 90:
print("Exact score!")
Output
Exact score!Notice
if score == 90: asks a question and ends with a colon. The repaired program prints Exact score!.
Two possible paths
An else block supplies the path used when the if condition is False.
Minimal: choose between two messages.
has_ticket = True
if has_ticket:
print("Enter")
else:
print("Stop")
Output
EnterNotice
Exactly one branch runs: the if branch for True, or the else branch for False.
One twist: the condition can compare a saved number.
items = 7
if items % 2 == 0:
print("Even group")
else:
print("Odd group")
Output
Odd groupNotice
The remainder decides which of the two labels is printed.
Realistic: include the given value in the chosen output.
balance = 12
if balance >= 10:
print(f"Balance {balance}: ready")
else:
print(f"Balance {balance}: add funds")
Output
Balance 12: readyNotice
Both branches use the same fixed value, but only the matching message appears.
Real input: try the two-path score decision with a score supplied by a person.
Try it yourself
score = int(input("Score: "))
if score >= 10:
print("Ready")
else:
print("Keep practicing")
Notice
int() converts the typed score before the comparison, and only one message prints.
Lesson 2: Choose among Several Paths
Logical operators
and is True only when both joined tests are true. or is True when at least one joined test is true. not reverses a boolean: True becomes False, and False becomes True.
Minimal: join two booleans with and.
print(True and False)
Output
FalseNotice
and prints False because both booleans must be True.
One twist: use or so one true boolean is enough.
print(True or False)
Output
TrueNotice
or prints True because its first boolean is True.
Minimal: reverse one boolean.
print(not True)
Output
FalseNotice
not True prints False because not flips the boolean.
One twist: use not in a condition.
has_ticket = False
if not has_ticket:
print("Ticket needed")
Output
Ticket neededNotice
has_ticket is False, so not has_ticket is True and the indented message runs.
Another twist: check that a value is in an inclusive range.
x = 7
in_range = x >= 0 and x <= 10
print(in_range)
Output
TrueNotice
x >= 0 and x <= 10 makes both boundary tests visible. The chained form 0 <= x <= 10 asks the same inclusive-range question.
One twist: write the same inclusive range as a chained comparison.
x = 7
print(0 <= x <= 10)
Output
TrueNotice
The chained comparison prints True because 7 is between both endpoints.
Realistic: accept either of two exact values.
day = "Saturday"
if day == "Saturday" or day == "Sunday":
print("Weekend")
else:
print("Weekday")
Output
WeekendNotice
One true equality is enough for the or condition to choose Weekend.
One twist: compare two strings that differ only in capitalization.
print("Yes" == "yes")
Output
FalseNotice
Equality is case-sensitive, so "Yes" == "yes" prints False.
Real input: try the weekend decision with a day supplied by a person.
Try it yourself
day = input("Day: ")
if day == "Saturday" or day == "Sunday":
print("Weekend")
else:
print("Weekday")
Notice
This cell asks for a day when you run it yourself; the decision still prints one label.
elif runs top-down
An elif adds another condition between if and else. Python checks the conditions from top to bottom, then runs the first matching branch and skips the rest.
Minimal: classify a score with the highest threshold first.
score = 95
if score >= 90:
print("Level A")
elif score >= 80:
print("Level B")
else:
print("Keep practicing")
Output
Level ANotice
95 also satisfies score >= 80, but the score >= 90 branch matches first. Python prints only Level A. Order matters.
One twist: add another threshold while keeping the checks highest-to-lowest.
score = 84
if score >= 90:
print("Gold")
elif score >= 80:
print("Silver")
elif score >= 70:
print("Bronze")
else:
print("Practice round")
Output
SilverNotice
84 misses the first test and matches the second, so later branches are not checked.
One twist: an elif can require two tests at once with and.
score = 84
if score >= 90:
print("Gold")
elif score >= 80 and score < 90:
print("Silver")
else:
print("Practice round")
Output
SilverNotice
Both parts of the elif are true for 84, so the Silver branch runs. The < 90 half is already guaranteed by the top-down order — it is written here only to show and inside an elif; in your own ladders, leave it out.
Realistic: a triangle label must check the most specific equality pattern first.
side_a = 5
side_b = 5
side_c = 5
if side_a == side_b and side_b == side_c:
print("Equilateral")
elif side_a == side_b or side_a == side_c or side_b == side_c:
print("Isosceles")
else:
print("Scalene")
Output
EquilateralNotice
An equilateral triangle also has pairs of equal sides. Checking equilateral first prevents the broader isosceles branch from taking it. The elif condition uses or: any one of its three equal-side tests is enough.
Lesson 3: Build Clear Decision Trees
Nested decisions
A decision can contain another decision. The extra indentation shows that the inner question is asked only inside the chosen outer branch.
Minimal: check permission before checking a score.
has_permission = True
score = 8
if has_permission:
if score >= 7:
print("Activity unlocked")
Output
Activity unlockedNotice
The score test is reached only after permission is confirmed.
One twist: give each decision its own alternative.
has_permission = True
score = 5
if has_permission:
if score >= 7:
print("Activity unlocked")
else:
print("Raise the score")
else:
print("Permission needed")
Output
Raise the scoreNotice
Each else lines up with the condition it belongs to. Here permission is present, so the inner else prints.
Realistic: use fixed stand-in text in a nested choice.
age_text = "12"
age = int(age_text)
has_pass = True
if age >= 10:
if has_pass:
print("Entry approved")
else:
print("Pass needed")
else:
print("Age requirement not met")
Output
Entry approvedNotice
The fixed text "12" becomes an integer before the outer decision. Then the inner decision checks the pass.
Real input: try the nested entry decision with an age supplied by a person.
Try it yourself
age_text = input("Age: ")
age = int(age_text)
has_pass = True
if age >= 10:
if has_pass:
print("Entry approved")
else:
print("Pass needed")
else:
print("Age requirement not met")
Notice
The typed age becomes an integer before the outer decision; the inner decision runs only when the age qualifies.
Return to the leap-year rule
The calendar rule joins two paths: a year passes when it is divisible by 4 and not divisible by 100, or when it is divisible by 400. Parentheses keep the first pair together.
Minimal: check whether one year is divisible by 4.
year = 2024
print(year % 4 == 0)
Output
TrueNotice
2024 has no remainder after division by 4, so the comparison prints True.
One twist: exclude centuries from the divisible-by-4 rule.
year = 2024
print(year % 4 == 0 and year % 100 != 0)
Output
TrueNotice
Both tests are true for 2024, so this partial rule prints True.
One twist: include years divisible by 400 again.
year = 2024
print((year % 4 == 0 and year % 100 != 0) or year % 400 == 0)
Output
TrueNotice
The complete rule prints True; the or adds the every-400th-year exception. When and and or share a condition, and binds tighter than or; use parentheses to show the intended groups.
Minimal: save the boolean for one given year.
year = 2024
is_leap_year = (year % 4 == 0 and year % 100 != 0) or year % 400 == 0
print(is_leap_year)
Output
TrueNotice
The full test prints True for the single given year 2024.
One twist: use the saved boolean to choose one exact label.
year = 2024
is_leap_year = (year % 4 == 0 and year % 100 != 0) or year % 400 == 0
if is_leap_year:
label = "leap year"
else:
label = "common year"
print(label)
Output
leap yearNotice
The calculation and the message choice have separate jobs, which makes the rule easier to read.
Realistic: print the full calendar message from the opening.
year = 2024
is_leap_year = (year % 4 == 0 and year % 100 != 0) or year % 400 == 0
if is_leap_year:
print(f"{year} is a leap year.")
else:
print(f"{year} is not a leap year.")
Output
2024 is a leap year.Notice
One condition controls two possible messages, and exactly one appears.
Real input: try the full calendar decision with a year supplied by a person.
Try it yourself
year = int(input("Year: "))
is_leap_year = (year % 4 == 0 and year % 100 != 0) or year % 400 == 0
if is_leap_year:
print(f"{year} is a leap year.")
else:
print(f"{year} is not a leap year.")
Notice
Converting the typed year lets the same full rule choose one calendar sentence.
Final build
Run the calendar decision with the fixed year 2024. Read the condition aloud: divisible by 4 and not by 100, or divisible by 400.
# One given year; no repeated testing is needed.
year = 2024
is_leap_year = (year % 4 == 0 and year % 100 != 0) or year % 400 == 0
if is_leap_year:
print(f"{year} is a leap year.")
else:
print(f"{year} is not a leap year.")
Output
2024 is a leap year.Notice
Comparisons create booleans, logical operators combine them, and the decision selects the one matching output.
Recap
ifruns a block when its condition is true.elifchecks another path;elsehandles what remains.and,or, andnotcombine or reverse yes-or-no tests.- An indented decision inside another decision asks a second question.
- Branch order matters because Python takes the first matching path.
You can now build a calendar decision that identifies a leap year.