Embercode
✕
ConditionalsLesson 4 of 14

50 min read

Classic conditional problems in Python

Why it matters

Small decision problems appear in screening tests and technical interviews again and again. A recruiter gives you a sentence of rules and asks for a program. Your score depends less on knowing a special language feature and more on whether you turn the rules into correct conditions, put them in a sensible order and test the edges. This lesson is a set of such problems, solved step by step, with the reasoning shown.

The previous lessons gave you the tools: if, elif, and, or, match and the conditional expression. This lesson shows how to choose among those tools for a real rule. It also shows the habits that separate a correct answer from a lucky one: checking invalid input first, ordering the tests from the most specific to the most general, and tracing each test case by hand before you run anything.

A method for any rule problem

Every problem in this lesson follows the same process. Learn the process once, and you can apply it to a problem you have never seen.

  1. Write the rules in plain sentences. Do not start coding until each rule is a sentence you could read aloud to a friend.
  2. Name the inputs and outputs. Decide the variable names, the types and the exact text the program prints.
  3. Find the invalid inputs. Ask what values should be rejected, such as zero, negative numbers, or a month of 13. Those checks go first.
  4. Order the rules from specific to general. A rule that covers a small set of cases must come before a rule that covers a large set. Otherwise the general rule catches the special cases first.
  5. List the test inputs. Choose ordinary values, boundary values and invalid values. Boundary values are the cases where a rule changes, such as 0, 1, 99, 100 or February 29.
  6. Trace each test by hand. For each test input, write down every condition's result and the output. Only then run the program. If the trace and the run disagree, the bug is in your trace or in your code, and you learn from either.

Step 6 feels slow at first. It is the most valuable habit in this lesson. When you can predict the output of every test, the computer's output becomes a check, not a surprise.

Leap year

Rule: a year is a leap year when it is divisible by 400, or when it is divisible by 4 but not by 100.

Divisible means the remainder is 0. The % operator gives the remainder, so year % 4 == 0 is the test for divisible by 4. Why the extra rules? The Earth takes about 365.2422 days to orbit the Sun. Adding a day every four years is almost right, but it adds about three days too many every 400 years. Skipping the leap day in century years that are not divisible by 400 fixes that error, which is why 1900 was not a leap year and 2000 was.

Here is the program. When it asks for a year, type 1900. Then type 2000, 2024 and 2023 to test the other branches.

year = int(input())
 
leap = (year % 4 == 0 and year % 100 != 0) or year % 400 == 0
print(f"{year} is a leap year" if leap else f"{year} is not a leap year")

The output for 1900 is:

1900 is not a leap year

Trace the rule as a tree of questions. The first question is "is the year divisible by 4?" For 1900 the remainder is 0, so the answer is yes. The second question is "is it divisible by 100?" For 1900 it is yes, so the phrase "but not by 100" fails, and the first half of the rule is false. The third question is "is it divisible by 400?" For 1900 the remainder is 300, so the answer is no. Both halves of the rule are false, so the year is not a leap year.

Now trace 2000. It is divisible by 4 and by 100, so the first half is false. It is divisible by 400, so the second half is true. The whole rule is true, and the output is 2000 is a leap year. This is why 2000 is a leap year but 1900 is not.

The tree has the same shape as the code. The first question, "divisible by 4", is the test before the and. The second question, "divisible by 100", is the test after the not. The third question, "divisible by 400", comes after the or.

The brackets in the code matter. The rule is (four and not hundred) or four-hundred. and binds tighter than or in Python, so the brackets around the first part are not strictly needed, but they make the rule readable. Remove the second part, or swap or for and in the wrong place, and the answers change. Test values such as 1900, 2000, 2024 and 2023 expose each mistake. A good test set always includes at least one century year that is a leap year and one that is not.

The most common wrong version is this one:

leap = year % 4 == 0 or year % 100 != 0

Trace 1900 with this wrong formula. 1900 % 4 == 0 is True, so the whole or is True, and the program says 1900 is a leap year. The wrong formula uses or where the rule needs and, and it leaves out the 400 rule. Test it on 1900 and it fails immediately, which is why the test set above is worth writing before you code.

Python · editable
True

Type of triangle

Rule: three side lengths make a real triangle only when every side is greater than zero, and each pair of sides adds up to more than the third side. A valid triangle is then equilateral when all three sides are equal, isosceles when exactly two sides are equal, and scalene when no two sides are equal.

The validity check comes first. The three types are only meaningful once you know the sides form a triangle. Within the valid ones, the equilateral test must come before the isosceles test. An equilateral triangle also has two equal sides, so the isosceles test would be true for it, and the first matching branch would win. The order of the elif ladder is the whole trick of this problem.

Run this program with the input 5 5 8. map(int, input().split()) splits the typed line at the spaces and converts each part to a whole number. Python lets you write a == b == c to mean a == b and b == c, which is a chained comparison.

a, b, c = map(int, input().split())
 
if a <= 0 or b <= 0 or c <= 0 or a + b <= c or a + c <= b or b + c <= a:
    print("Not a triangle")
elif a == b == c:
    print("Equilateral")
elif a == b or b == c or a == c:
    print("Isosceles")
else:
    print("Scalene")

The output is:

Isosceles

Trace 5 5 8. Step 1, validity: no side is 0 or less. a + b is 10, which is more than 8. a + c is 13, which is more than 5. b + c is also 13, which is more than 5. Every pair passes, so the first if is False. Step 2: are all sides equal? 5 == 5 is True, but 5 == 8 is False, so the equilateral test is False. Step 3: are any two sides equal? a == b is True, so the isosceles branch prints Isosceles.

Now try 5 5 5. The validity check passes. The equilateral test is a == b == c, which is True, so the output is Equilateral. If the isosceles test came first, the output would be Isosceles, which is wrong. Try 3 4 5 for the scalene case, and 1 2 3 for the invalid case. The sum 1 + 2 equals 3, which is not more than 3, so the triangle is invalid. This is a classic boundary: the rule says "more than", so equal sums fail.

The same logic as a function, with each result tested, looks like this:

Python · editable
(5, 5, 8) isosceles
(5, 5, 5) equilateral
(3, 4, 5) scalene
(1, 2, 3) invalid
(0, 4, 4) invalid

Two more points matter for a careful answer. The first is the size of the numbers. In languages with fixed-size integers, a sum of two large numbers can exceed the largest value the type can hold, and then it wraps around to a negative number. Python integers have no fixed size, so 2000000000 + 2000000000 is exact:

Python · editable
4000000000

Python does not wrap around, so this problem has no overflow trap in Python. In an interview, still say what you assumed about the input size, because another language may be the one the interviewer asks about.

The second point is that the validity check uses <=, not <. A triangle with sides 1, 2 and 3 is not real, because the two short sides lie flat along the long side. The <= in the test covers this case.

Character classes

Rule: classify one character as a digit, an upper-case letter, a lower-case letter or something else.

Characters are numbers underneath. Each character has a code, and '0' through '9' have consecutive codes, as do 'A' through 'Z' and 'a' through 'z'. Python lets you compare characters with <= and >=, and that gives you ranges directly. The test "0" <= ch <= "9" is True for every digit.

Run the program below. input()[0] keeps the first character of the typed text. When it asks for a character, type 7 and press Enter. Then type Q, q and #.

ch = input()[0]
 
if "0" <= ch <= "9":
    print("digit")
elif "A" <= ch <= "Z":
    print("upper-case letter")
elif "a" <= ch <= "z":
    print("lower-case letter")
else:
    print("something else")

For 7 the output is digit. For Q it is upper-case letter, and for # it is something else. Trace Q. The digit test asks whether "Q" is between "0" and "9". Its code is 81, and the code of "0" is 48, so the test is 48 <= 81 <= 57, which is False because 81 is not at most 57. The upper-case test is "A" <= "Q" <= "Z", which is True, so the program prints upper-case letter.

The ranges are one of the few places where ordering does not change the answer, because the groups do not overlap. Still, put the most common case first in a real program, because a reader scans the top of the code first. Python also has string methods such as isdigit, isupper, islower and isalpha. Those are the better choice in everyday code, and writing the ranges yourself shows the underlying idea. In an interview, mention the method after you have written the ranges.

Python · editable
True True False

The method isdigit accepts more than the characters 0 to 9. It accepts some other characters too, such as the superscript two, ². The method isdecimal is stricter for this purpose, and the two give different answers for that character:

Python · editable
True False

Check the method's meaning before you rely on it for a range. The range test "0" <= ch <= "9" never accepts ², because its code is outside the range.

A common mistake in Python is to compare a whole string when you meant one character. A string is compared letter by letter, from the left, so "10" is less than "9". The first characters decide: "1" comes before "9".

Python · editable
True

The text "10" is not a digit, but the test says it is. The fix is to check the length, so that only one character is compared:

Python · editable
False True

Month length

Rule: April, June, September and November have 30 days. February has 29 days in a leap year and 28 otherwise. Every other month has 31 days.

This problem combines the leap-year rule with a match statement. Python's match checks a value against a list of cases, and it works like a switch. It has one important difference: a case never falls through to the next one. Each case runs its own block and stops. To share one block between several months, list them in one case with |, which means "or". The 30-day months share one case. February needs the leap test. Compute the leap test first, store it in a variable, and then choose the number of days.

When the program asks for two numbers, type 2 2024. The first number is the month and the second is the year.

month, year = map(int, input().split())
 
leap = (year % 4 == 0 and year % 100 != 0) or year % 400 == 0
 
match month:
    case 4 | 6 | 9 | 11:
        days = 30
    case 2:
        days = 29 if leap else 28
    case _:
        days = 31
 
print(f"Month {month} of {year} has {days} days")

The output is:

Month 2 of 2024 has 29 days

Trace it. month is 2 and year is 2024. The leap test is True, since 2024 is divisible by 4 and not by 100. The match looks for a case that matches 2, and case 2 matches. It sets days to 29 if leap else 28, which is 29. The other cases are skipped, and the print shows the result.

Type 4 2023 next. The month 4 is in the group with 30 days. The output is Month 4 of 2023 has 30 days. Type 2 1900. February of 1900 is not a leap year, so the output is 28 days.

The last case, case _:, is the default. The underscore matches anything, so it catches every month that no earlier case matched. Without it, a month of 13 matches no case, and days is never set. Python does not check that at the time you write the code. The error appears when the program runs and reads days. Here is a function without the default:

Python · editable
30
Traceback (most recent call last):
  ...
UnboundLocalError: cannot access local variable 'days' where it is not associated with a value

The lines above the error name your file, and they are omitted here. The error text is the part to read. It says that days was never given a value on this path. The fix is a case _: default, or a check before the match that rejects a month outside 1 to 12. The first output line is 30, because April is in the 30-day group.

Python's standard library also knows the month lengths. The calendar module has a function for them. It returns the weekday of the first day of the month as a number (Monday is 0), and then the number of days, so the day count is the second item:

Python · editable
(3, 29)
(2, 28)

In real code, use the library. Write the match version in an exercise or an interview, because it shows the rule.

The next date

Rule: given a valid date, print the next day. Remember that months have 28, 29, 30 or 31 days, that February has 29 days in a leap year, and that 31 December is followed by 1 January of the next year.

This is the hardest problem in the lesson, and it is a real interview favourite. It combines every idea so far: a helper function that answers a question, a match statement, an if for the end of a month and a nested if for the end of a year. The trick is to add one day first, then fix the result if it runs past the end of the month.

The program below reads a date such as 31 12 2024 and prints the next day. When it asks for the date, type 31 12 2024.

def is_leap(year):
    return (year % 4 == 0 and year % 100 != 0) or year % 400 == 0
 
def days_in_month(month, year):
    match month:
        case 4 | 6 | 9 | 11:
            return 30
        case 2:
            return 29 if is_leap(year) else 28
        case _:
            return 31
 
day, month, year = map(int, input().split())
 
day = day + 1
if day > days_in_month(month, year):
    day = 1
    month = month + 1
    if month > 12:
        month = 1
        year = year + 1
 
print(day, month, year)

The output is:

1 1 2025

Trace it, one line at a time. The input gives day 31, month 12 and year 2024. The line day = day + 1 makes day 32. The function days_in_month(12, 2024) goes to the case _: default, because 12 is not in the 30-day group and is not February, and returns 31. Is 32 greater than 31? Yes. So day becomes 1, month becomes 13, and the nested test month > 12 is True. Then month becomes 1 and year becomes 2025. The output is 1 1 2025.

Now trace 28 2 2024. The day becomes 29. The function gives 29 for February of a leap year. Is 29 greater than 29? No. The date is unchanged except for the day, and the output is 29 2 2024. Try 28 2 2023 as well. The function gives 28, so 29 is greater than 28, and the program moves to 1 March. The output is 1 3 2023.

Two design choices make this program correct. The first is adding one day before any test. That keeps the logic in one direction: check whether the new day passed the end of the month. The second is putting the end-of-year test inside the end-of-month test. Only a month that has just ended can roll into a new year, so the nested test is safe.

The functions keep the main code short. is_leap is the same rule you saw above, now named so the reader knows its job. days_in_month returns a number from a match. Each return ends the function, so the match needs no break, and the cases do not fall through. The case _: default returns on every path, so the function always returns a number.

The problem does not check that the input date is valid. For a real program, add checks first. Reject a month outside 1 to 12, a day below 1 or above the month's length, and a year outside a range you support. Each check is an if that stops early. The checks go before any date arithmetic, which is the guard clause pattern discussed below.

Here are the boundary cases to test before you trust the program. Test 31 12 2024, which crosses a year. Test 28 2 2023 and 28 2 2024, which test the leap rule. Test 30 4 2024, which crosses a 30-day month. Test 31 1 2024, which crosses a 31-day month into February. Test 15 6 1900, which tests a century year. Each one exercises a different branch.

Here are the results of those tests, from the same rules:

Python · editable
(31, 12, 2024) -> (1, 1, 2025)
(28, 2, 2024) -> (29, 2, 2024)
(28, 2, 2023) -> (1, 3, 2023)
(30, 4, 2024) -> (1, 5, 2024)
(31, 1, 2024) -> (1, 2, 2024)
(15, 6, 1900) -> (16, 6, 1900)
(31, 12, 9999) -> (1, 1, 10000)

The last line shows that the year grows to five digits without trouble, because Python integers have no fixed size.

Python's datetime module already does this arithmetic. Adding one day to a date gives the next date, including the leap years:

Python · editable
2025-01-01
2024-02-29

Use the library in real code. The hand-written version is the one to know for an interview, because it shows each rule.

A guessing game uses the same decisions

Guessing a number is a good model of a sorted search. You think of a number from 1 to 100, and the program guesses. Each reply, "higher", "lower" or "correct", is a branch. A good strategy guesses the middle of the range that is still possible, so each reply throws away half of the remaining values. This strategy is called binary search, and the code for each step is an ordinary conditional inside a loop.

The program below uses a fixed secret, 37, so the steps are the same every time. Read each line of the output and check that the range shrinks by half.

Python · editable
guess 50, range 1 to 100
guess 25, range 1 to 49
guess 37, range 26 to 49
found 37 in 3 steps

The // operator divides and drops the decimal part, so (1 + 100) // 2 is 50. Each elif and else removes the half that cannot hold the secret. A list of 1000 values needs at most about ten steps, and a list of a million needs about twenty. The number of steps grows slowly, because each step halves the work.

Guard clauses and the order of checks

A guard clause is a check at the top of a function that handles an invalid or special case and then returns early. The guard keeps the normal logic flat, with no deeply nested blocks. The triangle rule above is written with guards in the function triangle_kind. Each block returns, so no else is needed. The reader can read top to bottom, and each check handles one case.

Guards are the most useful habit for classic problems. Put every invalid input at the top, then the special cases from specific to general, and the ordinary case last.

The order of the tests in a guard is still important. In the version below, the isosceles test comes before the equilateral test. Every equilateral triangle returns isosceles, because the first matching return wins:

Python · editable
isosceles

The rule is the same as for the elif ladder: once a branch returns, nothing below it runs.

Thinking in decision trees

Many classic problems are easiest to understand as a tree. Each question has a yes branch and a no branch, and the end of each branch is an output. When you cannot see the order of your tests, draw the tree on paper. Each path from the top to an output is one case, and you can count the cases to make sure none are missing.

The leap-year rule as a tree:

divisible by 4?
  no  -> not a leap year
  yes -> divisible by 100?
           no  -> leap year
           yes -> divisible by 400?
                    yes -> leap year
                    no  -> not a leap year

The tree has four paths, and each one ends at one answer. Check each path against the rule. A year such as 2023 takes the first path. A year such as 2024 takes the second. A year such as 2000 takes the third. A year such as 1900 takes the fourth.

A decision tree also makes the cost clear. A tree with a fixed number of questions costs the same amount of work for every input. The leap-year test asks at most three questions. The triangle test asks at most about ten. None of these depends on the size of the input, so they run in constant time, O(1). Time complexity measures how much work grows as the input grows, and a fixed number of questions does not grow.

Interview questions and patterns

The classic problems are favourites in interviews because they reward clear thinking and calm testing. Here are the patterns to name when you answer.

The rule table. When a problem has several rules, write a table of inputs and outputs first. Each row is a test case. The table shows the boundaries and the order at a glance, and you can write the code by reading the table row by row.

The fixed-width calendar. Month-length and next-date problems are about data that never changes: the number of days in each month. Store the data in a match, a dictionary or a list, and test the lookup with a few months. Mention that the leap rule is the only exception.

The string trap. Whenever the input is text, ask whether you are comparing one character or a whole string. The "10" example above shows how a whole-string comparison goes wrong. Mention the length check in your answer.

The character range. Comparing characters with ranges works because the codes are consecutive. Say this in your answer, and then mention the string method that does the same job.

For time and space, all of these classic problems are O(1), because the work is a fixed set of tests, and the space is a fixed set of variables. The complexity answer is short, and you should give it with the reason.

Real-world uses

These problems are not toys. The leap-year rule is inside every date library, and the next-date logic is inside calendars, billing systems and scheduling tools. Triangle classification appears in graphics and drawing software when a shape must be checked before it is drawn. Character classes appear in form validation: a password field checks for a digit and an upper-case letter. Month lengths appear in payroll, subscription renewals and tax years.

In each case the same habits apply. The rule is written down, the invalid input is rejected first, the tests run from specific to general, and the boundaries are checked. When a real bug appears, it is almost always one of the boundaries or one of the orders.

Write the test table before the code

The method at the start of this lesson, with its test table, is worth practising until it feels automatic. Take the leap-year rule. Before you write one line of Python, write the table below. Each row is a year. Each column records whether the year is divisible by 4, by 100 and by 400, and the last column is the expected answer. Fill in the expected answer by applying the rule in words, not by looking at your code.

year   by 4   by 100   by 400   expected
1900   yes    yes      no       common
2000   yes    yes      yes      leap
2023   no     no       no       common
2024   yes    no       no       leap
2100   yes    yes      no       common
2400   yes    yes      yes      leap

The table covers the usual year, the century years that are leap and not leap, and the 400 rule. The rows 1900 and 2000 are the most important pair in the table, because they differ only in the 400 column. A program that gets one of them wrong has the rule wrong. Once the table is written, the code is almost a copy of the columns: year % 4 == 0, year % 100 == 0 and year % 400 == 0, combined with the rule in the expected column.

Notice what the table lets you check without running anything. For 2024, the rule needs "by 4 yes and by 100 no", which is True, so the expected answer is leap. For 2100, the rule needs "by 4 yes and by 100 no", which is False, and the 400 column is no, so the answer is common. The table turns the rule into an exercise you can do on paper, and you can compare the program against it row by row.

Why the next-date program uses helper functions

The next-date program has two helper functions, one for the leap year and one for the number of days in a month. Helpers are a good habit even in short programs. Each helper answers one question with a name the reader can trust. The main code then reads like the rule: add one day, check the month length, roll over the month, roll over the year. A reader does not have to hold the leap rule and the match in their head at the same time.

The helpers also make testing easier. You can change the leap rule in one place and every use follows. You can call days_in_month(2, 2023) in a test to check February directly, without setting up a whole date. In an interview, a clear split like this earns credit even when the logic is the same as one long function.

A method for the interview answer

When an interviewer asks a classic problem, the answer is judged on process as much as on the final code. A strong answer follows this order. First, restate the rule in your own words and ask one question about any unclear point, such as whether the range is inclusive. Second, list the inputs you will reject. Third, name the order of the tests and say why. Fourth, write the code. Fifth, trace two or three inputs out loud, including a boundary. Sixth, state the time and space cost.

Saying each step out loud shows the reasoning, and the trace is the part that catches bugs in front of the interviewer. A candidate who traces a boundary and fixes an off-by-one error while speaking often scores higher than one who writes a correct program in silence and cannot explain it.

Common mistakes and the messages Python gives you

Mistake 1: the wrong leap-year formula. Using or where the rule needs and, or leaving out the 400 rule. This gives no error, only a wrong answer. Trace 1900 and 2000 to catch it.

Mistake 2: the general case first. Writing the isosceles test before the equilateral test. Every equilateral triangle is reported as isosceles, and the program never tells you why.

Mistake 3: the boundary operator. Writing sum < c where the rule says "more than", or sum <= c where the rule needs equality to count. Test the exact boundary, such as 1 2 3.

Mistake 4: reading the input as text. The input function returns a string, so year = input() followed by year % 4 fails with TypeError: not all arguments converted during string formatting. Convert the input with int first.

Mistake 5: the string comparison trap. Comparing "10" with "0" and "9" as if it were one character, which gives True. Check the length, as shown above.

Mistake 6: a missing default in a month match. The function fails with UnboundLocalError: cannot access local variable 'days' where it is not associated with a value. Add a case _: default, or reject invalid months first.

Mistake 7: a block with no indented body. A line such as if True: with the next line not indented fails with IndentationError: expected an indented block after 'if' statement on line 1. Indent the body under the if.

Mistake 8: return at the top of a file. A return outside a function fails with SyntaxError: 'return' outside function. Put the code in a function such as main.

Debugging walkthrough: the equilateral triangle that says isosceles

A student reports that the input 5 5 5 prints Isosceles. The student expects Equilateral. The code is this:

a, b, c = map(int, input().split())
 
if a <= 0 or b <= 0 or c <= 0 or a + b <= c or a + c <= b or b + c <= a:
    print("Not a triangle")
elif a == b or b == c or a == c:
    print("Isosceles")
elif a == b == c:
    print("Equilateral")
else:
    print("Scalene")

Step 1: write down the values. a, b and c are all 5.

Step 2: test the validity check. No side is 0 or less, and every pair sums to 10, which is more than 5. The first if is False.

Step 3: test the isosceles condition. a == b is True, so the second branch runs and prints Isosceles.

Step 4: the equilateral test was never reached. The elif ladder stopped at the first true branch.

The fix is to swap the two branches. Put a == b == c before a == b or b == c or a == c. Now 5 5 5 prints Equilateral, and 5 5 8 still prints Isosceles. Test both inputs after the change. A fix that makes one case right and breaks another is common, and running the whole test set is how you catch it.

Practice

Each exercise has an answer below a "Show the answer" line. Try each one first, then read the answer. Type the sample inputs when the program asks for them.

  1. Read a year and print century if it is divisible by 100, and not a century otherwise. Test 1900, 2000 and 2024.

Show the answer

year = int(input())
if year % 100 == 0:
    print("century")
else:
    print("not a century")

The outputs are century, century and not a century. Years divisible by 100 are the ones the leap rule treats specially.

  1. Write the leap-year test as one expression that is True for 2000 and False for 1900, and store it in a variable named leap. Explain why the brackets matter.

Show the answer

leap = (year % 4 == 0 and year % 100 != 0) or year % 400 == 0

The brackets make the two halves of the rule clear. Python would still group and before or, but a reader should not have to know that to understand the rule.

  1. Read three integers and print valid if they can form a triangle, and invalid otherwise. Test 1 2 3, 2 2 3 and 0 4 4.

Show the answer

a, b, c = map(int, input().split())
if a > 0 and b > 0 and c > 0 and a + b > c and a + c > b and b + c > a:
    print("valid")
else:
    print("invalid")

The outputs are invalid, valid and invalid. 1 2 3 fails because 1 + 2 is not greater than 3.

  1. Read one character and print vowel for a, e, i, o and u in either case, consonant for other letters, and not a letter for everything else.

Show the answer

c = input()[0].lower()
if c in ("a", "e", "i", "o", "u"):
    print("vowel")
elif "a" <= c <= "z":
    print("consonant")
else:
    print("not a letter")

Use a tuple of vowels, not the text "aeiou". The expression "" in "aeiou" is True in Python, because the empty string is part of every string, so an empty answer would count as a vowel. The vowel test must come first, because every vowel is also in the letter range.

  1. Write a function days_in(month) that returns the number of days for a month in 2023, using match. The 30-day months should share one case.

Show the answer

def days_in(month):
    match month:
        case 4 | 6 | 9 | 11:
            return 30
        case 2:
            return 28
        case _:
            return 31

Since 2023 is not a leap year, February is always 28. The case _: default returns 31 for the other months.

  1. Explain why the next-date program adds one day before it checks the month length, and say what would go wrong if it checked first.

Show the answer

The test needs the new day to ask whether it passed the end of the month. If the check came first, the program would compare the old day, which is always inside the month, and never roll over. Adding first, then checking, is the correct order.

  1. Add a check to the next-date program that rejects a month outside 1 to 12 and a day below 1, and prints bad date for those inputs. Put the check in a function, because return is not allowed at the top of a file.

Show the answer

def main():
    day, month, year = map(int, input().split())
    if month < 1 or month > 12 or day < 1:
        print("bad date")
        return
    # the normal date logic goes here
 
main()

The check is first, before the day is changed. The return ends main early, so no later line runs for a bad date.

  1. Work out by hand the output of the next-date program for these inputs: 28 2 2023, 28 2 2024, 30 4 2024 and 31 12 9999.

Show the answer

The outputs are 1 3 2023, 29 2 2024, 1 5 2024 and 1 1 10000. February 2023 has 28 days, so the 28th rolls over. February 2024 has 29 days, so the 28th does not. April has 30 days, so the 30th rolls over. The final case creates a fifth digit in the year, which Python handles without any change.

  1. A student wrote a check that a text value from input() is one digit, using text >= "0" and text <= "9". Explain why the check is wrong for the text 10, and fix it.

Show the answer

Strings are compared letter by letter, from the left. The text "10" starts with "1", which is between "0" and "9", so the whole check is True. The fix checks the length first: len(text) == 1 and "0" <= text <= "9". The text 10 has length 2, so the check is False.

  1. Does Python's match fall through from one case to the next, like a switch without break in other languages? Write a short example that shows the answer.

Show the answer

No. Each case runs its own block and then stops. The example below prints only february for month 2:

match 2:
    case 2:
        print("february")
    case 4:
        print("april")

To share one block between two cases, put them in one case with |, as in case 4 | 6 | 9 | 11:.

  1. A program reads a number of days, and the number must not be negative. Write the guard first, and print not allowed for a negative number. Explain why the guard comes first.

Show the answer

n = int(input())
if n < 0:
    print("not allowed")
else:
    print("the normal logic goes here")

The guard is first because the rest of the logic assumes a valid, non-negative number. Checking the input first keeps the main logic simple.

  1. A problem says each side of a triangle can be as large as 2000000000. A student asks whether the sums need a bigger integer type, as in some other languages. Answer the question and give the output of print(2000000000 + 2000000000).

Show the answer

No. Python integers have no fixed size, so the sum is exact. The output is 4000000000. The student should still state the input range in an interview, because the limit may matter in another language.

What you now know

  • Each classic problem is solved by writing the rule, rejecting invalid input, ordering the tests from specific to general, and tracing each edge case.
  • The leap-year rule is (divisible by 4 and not by 100) or divisible by 400, and 1900 and 2000 are the key test cases.
  • The triangle test checks validity first, then equilateral before isosceles, and uses > for the sum rule.
  • A character range such as "a" <= ch <= "z" works on one character. Check the length first, so a longer text is not compared as if it were a character.
  • The month-length problem uses match with | for the 30-day months, and a leap test for February.
  • The next-date problem adds one day first, rolls to the next month and year only when needed, and uses functions to keep the code short.
  • Guard checks reject bad input at the top, and decision trees help you check that every case is covered.
Practice this topic: coding problems for Conditionals →

Stuck? Ask the tutor

Ask about anything in this lesson. Answers are short and use simple words.

Sign in to ask the tutor.

← The ternary operator in Python