Skip to content
academia.sh

Lesson 06 / 18

Conditional Branching

Condition-dependent execution, multi-way selection, writing readable conditions, and mapping-based dispatch.

Contents

The programs written so far have followed a single path from start to end: each statement executed once, in its turn. Most problems, however, require different behavior depending on the situation. If a measurement is above a limit, a warning should be given; if it is below, nothing should happen.

This lesson covers how flow splits based on a condition. The previous course showed its hardware counterpart: a comparison instruction sets flags, and a conditional branch instruction changes the program counter only if the condition holds. Every construct here ultimately reduces to those instructions.

One-Way and Two-Way Selection

The simplest form is a block that executes only when a condition holds:

measurement = 25
LIMIT = 20

if measurement > LIMIT:
    print("limit exceeded")      # limit exceeded

A condition is any expression that produces a logical value. If it does not hold, the block is skipped and flow continues below it.

In two-way selection, a block to run when the condition does not hold is also defined:

if measurement > LIMIT:
    status = "high"
else:
    status = "normal"

print(status)                    # high

Which statements a block covers is shown differently by language: some use curly braces, some close the block with a keyword, Python uses indentation. The notation varies; the meaning is the same — the set of statements to execute depending on the condition is marked off.

In languages that use indentation, one warning applies: indentation is not mere formatting but meaning. A statement believed to be inside the block but whose indentation has shifted always runs regardless of the condition. This is a common instance of the logic error class defined in the first lesson.

Multi-Way Selection

When there are more than two cases, conditions are chained. In the chain, the first condition that holds wins; the rest are never tested.

def classify(measurement: int) -> str:
    if measurement < 10:
        return "low"
    elif measurement < 20:
        return "medium"
    elif measurement < 30:
        return "high"
    else:
        return "extreme"

print(classify(7), classify(12), classify(25), classify(40))
# low medium high extreme

The order of the chain determines the meaning of the conditions. The second branch’s condition measurement < 20, read on its own, is also true for the value 55; but by the time it is reached, the first condition is known not to hold, meaning measurement is at least 1010. Every branch implicitly carries the negation of all the ones before it.

This is why reordering breaks the program. The following chain always produces “extreme”, because its first condition is true for nearly every value:

def classify_wrong(measurement: int) -> str:
    if measurement >= 10:        # broad condition placed first
        return "extreme"
    elif measurement < 20:
        return "medium"
    else:
        return "low"

print(classify_wrong(12))        # extreme  — expected "medium"
print(classify_wrong(25))        # extreme  — correct for this value

The practical rule: conditions are ordered from the narrowest case to the broadest; the most general case is collected in the else branch. If the chain has no else branch at the end, input that satisfies no condition passes through silently, doing nothing — this is where overlooked cases typically hide. The last branch of every chain should state explicitly what happens for unexpected input.

Writing Readable Conditions

As a condition grows more complex, code readability drops quickly. Three habits prevent this.

Using the logical value directly. Writing if condition == True is unnecessary; condition is already a logical value. Adding the comparison lengthens the expression and risks accidentally turning into an assignment.

Naming the condition. A long condition is bound to a variable that states its meaning:

measurement, temperature = 25, 80

# Hard to read:
if measurement > 20 and temperature > 75 and not (measurement > 40):
    print("warning")

# Named:
limit_exceeded = measurement > 20
too_hot = temperature > 75
within_safe_range = measurement <= 40

if limit_exceeded and too_hot and within_safe_range:
    print("warning")             # warning

Simplifying negation. Nested negations are flattened using De Morgan’s laws: the expression not (a and b) is equivalent to (not a) or (not b). Which of the two forms to choose is decided by which one states the problem more directly.

The Conditional Expression

Conditional branching is a statement: it performs an action, it does not produce a value. Many languages, by contrast, also offer a form that produces a value depending on a condition. The difference between the two forms is a direct application of the statement–expression distinction from the first lesson.

measurement = 25

# Statement form: the status variable is bound separately in each branch.
if measurement > 20:
    status = "high"
else:
    status = "normal"

# Expression form: a single binding, the condition selects the value.
status = "high" if measurement > 20 else "normal"
print(status)                    # high

The expression form improves readability in short cases where a value is bound to one of two options: the variable is written once, and the binding is visibly in a single place.

It also has a limit. If the branches are long, nested, or contain side effects, the expression form becomes unreadable. The criterion is this: if the condition selects a value, use the expression form; if it selects an action, use the statement form.

Nested Conditions

Conditions can be nested inside one another. As depth grows, reading gets harder; each level adds an assumption the reader must keep in mind.

Condition stacks more than three levels deep are usually a sign that two separate problems are being solved in the same place. The standard way to reduce depth is to rule out invalid cases first and exit early. This pattern is revisited under the name guard clause in the functions topic; its counterpart here is turning nested branches into a flat chain.

Mapping-Based Dispatch

If a condition chain is nothing but a single value compared against constants, many languages offer a dedicated construct for it — switch-based selection or pattern matching. The construct’s name and capabilities vary by language.

An approach that offers an alternative to the chain is moving the selection into data:

UNIT_FACTOR = {"mm": 0.001, "cm": 0.01, "m": 1.0}

def to_meters(value: float, unit: str) -> float:
    if unit not in UNIT_FACTOR:
        raise ValueError(f"unknown unit: {unit}")
    return value * UNIT_FACTOR[unit]

print(to_meters(25, "cm"))       # 0.25
print(to_meters(1500, "mm"))     # 1.5

This form’s advantage is that adding a new unit is a data change, not a code change. A condition chain grows longer with every new case; a mapping table stays the same. This difference becomes more pronounced as the number of decisions grows; in the software design curriculum, the same idea is expanded under the heading of replacing conditionals with polymorphism.

The selection criterion is this: if the cases are fixed and few, a condition chain is clear; if the cases grow or change at run time, a mapping is preferred.

Summary

  • Conditional branching executes a block of code only when a condition holds; a condition is an expression that produces a logical value.
  • Block boundaries are shown differently by language; in languages that use indentation, indentation is part of the meaning.
  • In multi-way selection, the first condition that holds wins; every branch implicitly carries the negation of the ones before it, and reordering breaks the program.
  • Conditions are made readable by naming them and simplifying negations; a logical value is not compared with == True.
  • When a single value is compared against constants, a mapping table can replace a growing condition chain.

Next Step

Branching determines which path flow takes, but it executes each path only once. The measurement sequence in the shared problem, however, requires the same work for every element. The next lesson covers loops, which execute the same block repeatedly, and the guarantee that a loop terminates.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ Esc navigate · open · close