◀ Course contents Part 2 · Module 2-01

Making Decisions

The line between a program and a list of instructions

Everything so far ran top to bottom, every line, every time. An if statement is the first thing that changes that: from here on your code can look at a value and choose. That single idea is what separates a program from a recipe, and it is the foundation under every loop, filter and validation in the rest of this track.

Ready?

1

An if Runs a Block, Sometimes

An if takes a condition — anything that produces True or False — and runs the lines underneath it only when that condition holds.

temperature = 31

if temperature > 30:
    print("Heat warning")
    print("Sessions moved indoors")

print("Schedule published")

Two things are doing the work. The colon at the end of the if line says "a block follows". The indentation says which lines are in it — and in Python that is not a style choice, it is the syntax. Four spaces is the convention, and every line of the block must use the same amount.

In the example above, the two indented lines belong to the if. print("Schedule published") is back at the left margin, so it is outside the block and runs whatever the temperature is.

Why indentation instead of brackets

Most languages mark a block with braces and then let you indent however you like, which means the indentation can lie about what the code does. Python removes the gap by making the indentation be the structure. The cost is that a stray space is a syntax error. The benefit is that code which looks right is right.

IndentationError

if x > 3:
print("hi")

A colon promised a block and none arrived. Python stops before running anything.

Runs when x > 3

if x > 3:
    print("hi")

Indented, so it is inside the block.

Quick check

An if block has two indented lines, and a third line sits at the left margin. The condition is False. How many of the three run?

2

else Catches Everything Else

else gives you the other branch: it runs exactly when the if did not. Between them, one of the two always runs.

if score >= 60:
    print("Pass")
else:
    print("Fail")

elif — short for "else if" — is how you test a second, third or fourth condition. Python works down the chain and stops at the first one that is true, ignoring the rest even if they would also have matched.

if score >= 90:
    grade = "A"
elif score >= 75:
    grade = "B"
elif score >= 60:
    grade = "C"
else:
    grade = "F"

A score of 95 matches the first test and never reaches the others, which is why the chain does not need 90 > score >= 75 anywhere in it. Each branch may assume everything above it was false.

First match wins

Python stops at the first true test. Later branches are not considered.

One chain, one outcome

However many elifs there are, exactly one branch runs.

else is the safety net

Without one, a value nobody thought about falls through and nothing happens at all.

Separate ifs are different

Two if statements can both run. An if/elif pair cannot.

Quick check

With score = 95 and the grade chain above, which tests does Python actually evaluate?

3

Three Ways a Correct-Looking Chain Goes Wrong

The syntax is easy. These three are what actually cost time.

1. The chain is in the wrong order. Because the first match wins, a broad test placed above a narrow one swallows it. Written this way, nobody ever gets an A:

if score >= 60:
    grade = "C"
elif score >= 90:   # unreachable — 95 already matched above
    grade = "A"

The fix is to order the tests from most specific to least, which for numeric bands means starting at the top and working down.

2. = where == was meant. Python protects you here — if score = 60: is a SyntaxError rather than a silent assignment, which is one place Python is friendlier than C or JavaScript. Worth recognising the error message all the same.

3. Comparing to True. if is_active == True: works but says the same thing twice. if is_active: is the same test, and it is what everyone else writes. The same goes for the negative: if not is_active:.

Truthiness in a condition

An if does not need a comparison — it accepts any value and applies the rules from the data types module. 0, 0.0, "", None and empty collections are false; everything else is true. So if name: reads as "if there is a name", which is usually exactly right — but remember it cannot tell None from "", and sometimes that difference matters.

Quick check

A grade chain tests >= 60 first, then >= 90. What happens to a score of 95?

4

Shapes Experienced Programmers Write

Two patterns come up constantly, and both make code shorter and flatter without making it cleverer.

The guard. Deal with the impossible case first, on its own, and let everything after it assume the normal case:

if people == 0:
    print("Nobody signed up")
else:
    print(total / people)

The alternative is to nest the real work inside a check, which pushes it further right every time you add one. Flat code is easier to read, and the guard is how you keep it flat.

The conditional expression. When both branches do the same thing to a different value, Python has a one-line form:

label = "member" if is_member else "guest"

Read it as the value, then the condition, then the fallback. It is for choosing a value — if the branches do different work, use a normal if and keep it readable.

Two conditions, or one?

if signed_in: wrapped around if not suspended: does exactly what if signed_in and not suspended: does, in two more lines and one more level of indentation. Nesting is worth it only when the inner check genuinely does not make sense unless the outer one passed.

If the nested version has no else anywhere in it, it almost always wants to be one condition joined by and.

Quick check

When is x = a if cond else b the right thing to write?

0 of 9 completed

Real Python runs right here in your browser — nothing to install, nothing sent to a server. The interpreter downloads once the first time you press Run, then stays cached.

01

To do

An if takes a condition and runs the indented lines beneath it only when that condition is True.

if temperature > 30:
    print("Heat warning")

The colon says a block follows; the indentation says which lines are in it. Four spaces, and the same amount on every line of the block.

Your task: print Heat warning only when temperature is above 30, then print Schedule published every time. With the temperature at 31, both lines appear.

your_code.py
Python
Hint

if temperature > 30: on one line, then print("Heat warning") indented four spaces underneath it. Leave the last line where it is — it is outside the block.

Output

      
    02

    To do

    else runs exactly when the if did not. Between the two, one branch always runs — there is no way to fall through both.

    if score >= 60:
        print("Pass")
    else:
        print("Fail")

    Note that else is on its own line at the same indentation as the if, with its own colon and its own block.

    Your task: print Pass when score is at or above pass_mark, and Fail otherwise. With the values given, the output is one line: Fail.

    your_code.py
    Python
    Hint

    "At or above" is >=, not >. The else needs no condition of its own — it is everything the if did not catch.

    Output
    
          
      03

      To do

      Python's blocks are made of indentation, so getting it wrong is a syntax error rather than a style problem. Two different complaints show up:

      1

      expected an indented block

      A colon promised a block and the next line was flush left.

      2

      unexpected indent

      A line is indented with no if, else or other block header above it to belong to.

      Your task: this code has one of each. Run it, read the complaint, fix it, run again. The output should be:

      Low stock
      Reorder raised
      Done
      your_code.py
      Python
      Hint

      Both print lines belong inside the if, so both need the same four-space indent. The last line stays where it is.

      Output
      
            
        04

        To do

        elif tests another condition when the ones above it were false. Python works down the chain and stops at the first match, so each branch may assume everything above it failed.

        if score >= 90:
            grade = "A"
        elif score >= 75:
            grade = "B"
        else:
            grade = "F"

        Your task: set grade from score using four bands — A at 90 or above, B at 75 or above, C at 60 or above, and F below that. Print it. With the score at 78, the output is B.

        Write it as one chain, not four separate if statements.

        your_code.py
        Python
        Hint

        Start at the highest band and work down. Because the chain stops at the first match, the 75 test does not need an upper bound — anything 90 or over was already caught.

        Output
        
              
          05

          To do

          Because the first match wins, a broad test placed above a narrow one swallows it completely. The code below compiles, runs, raises nothing — and nobody in the history of the program has ever been given an A.

          This is the most common bug in conditional code, and it is invisible until someone checks the output against the spec.

          Your task: reorder the chain so the bands work. With score at 95 the output should be A. Keep all four branches and all four thresholds.

          your_code.py
          Python
          Hint

          Nothing is wrong with any individual test. Put the narrowest one — the highest threshold — at the top, and work downwards from there.

          Output
          
                
            06

            To do

            Some values make the rest of the code impossible. A guard deals with those first, on their own, so everything after it can assume the normal case.

            if people == 0:
                print("Nobody signed up")
            else:
                print(total / people)

            Without the guard this raises ZeroDivisionError — a crash, not a message, and one that reaches the person using the program rather than the person who wrote it.

            Your task: the code below divides before it checks. Run it, read the error, then guard it so it prints Nobody signed up instead of crashing. Do not change the two values.

            your_code.py
            Python
            Hint

            Wrap the division in an else, and put the zero case in the if above it. The average line never runs when people is 0, so the division never happens.

            Output
            
                  
              07

              To do

              A condition does not have to be a comparison. An if takes any value and applies the truthiness rules: 0, 0.0, "" and None are false, everything else is true.

              So if name: reads as "if there is a name", which is what experienced Python code writes. Two related habits go with it:

              1

              Never == True

              if is_active == True: says the same thing twice. if is_active: is the same test.

              2

              not for the negative

              if not name: rather than if name == "": — and it catches None as well.

              Your task: rewrite both conditions below in the plain form, without changing what they do. The output stays:

              No name given
              Account is active
              your_code.py
              Python
              Hint

              "if not name:" for the first, and "if is_active:" for the second. Both are the same test written the way everyone else writes it.

              Output
              
                    
                08

                To do

                Every nested if pushes the real work one level further right. Sometimes that structure is genuine. Often it is two conditions that wanted an and.

                The test is simple: if the inner if has no else, and the outer one has no other body, the two collapse into one.

                if signed_in:
                    if not suspended:
                        print("Welcome")

                Your task: flatten the three levels below into a single if with one condition. Same output, one level of indentation:

                Booking confirmed
                your_code.py
                Python
                Hint

                Join all three with and: signed_in and not suspended and seats_left > 0. Read left to right, it says exactly what the nesting said.

                Output
                
                      
                  09

                  To do

                  When both branches assign to the same name and differ only in the value, Python has a one-line form:

                  label = "member" if is_member else "guest"

                  Read it as: the value, then the condition, then the fallback. It is an expression, so it produces a value and can go anywhere a value can — including straight inside an f-string.

                  It is not a general replacement for if. The moment the two branches do different work rather than pick different values, the normal form says so more clearly.

                  Your task: rewrite the four-line if below as one conditional expression, and add fee, which is 0 for members and 250 for everyone else. Print both:

                  guest
                  250
                  your_code.py
                  Python
                  Hint

                  label = "member" if is_member else "guest" replaces all four lines. The fee follows the same shape: 0 if is_member else 250.

                  Output
                  
                        

                    The shipping router

                    To do

                    An order reaches the warehouse and something has to decide how it ships and what that costs. The rules below are the ones the business actually gave you, in the order they gave them.

                    The tier, in tier:

                    • OVERSIZE when the weight is over 20kg — whatever else is true
                    • otherwise EXPRESS when express was paid for
                    • otherwise STANDARD

                    The cost, built up in pieces:

                    • base500 when the country is JP, 1800 otherwise
                    • surcharge200 for every whole kilo above 2, so 4.2kg is two whole kilos over and costs 400
                    • express_fee1000 when express was paid for, 0 when not
                    • free_shippingTrue only when the order is worth 10000 or more and express was not paid for
                    • total — the three parts added up, or 0 when shipping is free

                    Then print exactly six lines:

                    Tier: EXPRESS
                    Base: 500
                    Surcharge: 400
                    Express: 1000
                    Free shipping: False
                    Total: 1900
                    your_code.py
                    Python
                    Hint

                    The tier is one if/elif/else chain, most specific first. For the surcharge, int(weight_kg - 2) gives the whole kilos over the limit — but a parcel under 2kg would make that negative, so guard it. The rest are conditional expressions or short if/else blocks, and free shipping needs `and not is_express`.

                    Output
                    
                          

                      Notification