Module 3 · Control Flow & Logic ⏱ 19 min

Making Decisions & Repeating Work

By the end of this lesson you will be able to:
  • Use if / elif / else to branch on a condition
  • Loop over a range and over the items of a list
  • Combine a loop and a condition to process data

Left to itself, a program runs straight down the page — every line once, in the order you wrote them. That is fine until the real world turns out to be less tidy. A pass mark depends on a score, a menu offers several choices, and the same calculation has to run for fifty rows of data instead of one. Two abilities lift a program past a flat script: branching, choosing a path from a condition, and looping, running a block more than once. Between them they account for most of what any useful program does.

A branch begins with if and a condition that is either true or false:

if score >= 60:
    print('pass')
else:
    print('try again')
flowchart TD
  A[score] --> B{score >= 90?}
  B -- yes --> G[grade = A]
  B -- no --> C{score >= 60?}
  C -- yes --> P[grade = Pass]
  C -- no --> F[grade = Fail]
  style B fill:#3776ab,color:#fff
  style C fill:#3776ab,color:#fff
An if / elif / else chain checks each condition in order and runs the first match.

How a branch chooses

Python checks each condition from top to bottom and runs the first one that is true, then skips everything below it. That order matters, so put the strictest test first. A grade scale that checks >= 60 before >= 90 would hand a 95 student an ordinary Pass, because the easy condition fires early and the sharper test never gets to run.

elif means else if and slots extra tests in between; else at the foot of the chain catches every case nothing else matched, and it takes no condition of its own. Each branch line ends in a colon, and the code that belongs to it sits indented underneath. Those indented lines are the body — the part that runs only when its branch is the winner.

A grade helper built from if / elif / else. The first true condition wins.
def grade_for(score):
    if score >= 90:
        return 'A'
    elif score >= 80:
        return 'B'
    elif score >= 60:
        return 'C'
    return 'F'

for s in [95, 72, 60, 40]:
    print(s, '->', grade_for(s))

elif is a chain, not a stack of ifs

Notice the helper above ended with a plain return 'F' after the chain. Once a branch matches, the rest of the chain is skipped, so a trailing else (or a line after the chain) catches everyone left over.

Beginners sometimes replace elif with a stack of separate if statements, and each one then runs on its own. For exclusive choices like letter grades that is a bug waiting to happen: a score of 95 satisfies >= 90 and >= 80 and >= 60, so every branch fires one after another. elif welds the tests into a single decision; separate if statements never do.

Conditions: comparisons, booleans, and the truth

A condition is anything Python can judge true or false. Comparisons do the heavy lifting: >= and <=, == for equal, != for not equal. Link several with and, when all of them must hold, or or, when any single one is enough, and turn a result around with not.

One habit pays for itself forever: test equality with ==, never a lone =. A single = assigns a value — score = 60 stores sixty, it does not ask whether score happens to be sixty. Confusing the two is the most common syntax error newcomers meet, and the interpreter says so plainly.

Truthiness: values that count as true or false

Python will also judge values that are not literally True or False. The number 0, an empty string, and an empty list all count as false; nearly everything else counts as true. Programmers lean on that — if name: reads as if a name was supplied — but it is a shorthand, not magic.

flowchart LR
  R["range(3) gives 0, 1, 2"] --> L1["for i in range(3)"]
  L1 --> O1["use the index"]
  S["a list of names"] --> L2["for name in names"]
  L2 --> O2["use the item"]
  style R fill:#3776ab,color:#fff
  style S fill:#3776ab,color:#fff
range hands the loop an index each step; looping over a list hands it the items directly.

Loops: counting, and walking a collection

A for loop runs its body once for every item in a sequence. Hand it range(n) and it counts from 0 up to, but never reaching, n — so range(3) produces 0, 1, 2, and stops there. That finish just short of n rule catches everyone once; it exists so the run holds exactly n values.

Hand the loop a list and it gives you each element in turn instead of bare indices. When a body needs both the item and its position, enumerate(names) yields index-and-item pairs. Choose the form that supplies exactly what the body needs and nothing more.

range gives indices; looping over a list gives the items themselves.
for i in range(3):
    print('count', i)

for city in ['Oslo', 'Cairo', 'Lima']:
    print(city, 'has', len(city), 'letters')

Looping with while

A for loop runs a set number of times, but you will often want to keep going until something changes without knowing in advance how many turns that takes. That is what while is for: it tests a condition before each pass and stops the instant the condition turns false. Put the test on the while header; the indented body underneath repeats for as long as that test still reads true.

The body owes the loop a duty the for never had: it must nudge the program closer to finishing on every single pass. Forget that line and the test never flips, so the loop runs forever. Programmers call that an infinite loop, and it is the classic while trap. Every loop you write deserves a clear path to its own exit.

A while loop tests its condition before every pass; the body must edge toward finishing.
count = 3
while count > 0:
    print(count)
    count = count - 1
print('lift off!')

Combining a loop with a condition

The real strength appears when a loop and a branch work together. A recurring shape is the accumulator: begin a running total at zero, walk the items one by one, and add each one that passes a test. The condition sits inside the loop, deciding for every item whether it counts toward the total.

Going wrong here looks like the bug in the exercise below — the code adds every number because the test was left out, so a mixed list sums as though all of its values qualified. The repair is a single indented if, yet it changes the answer entirely.

flowchart LR
  T["start: total = 0"] --> Q{"each number: is it even?"}
  Q -- "yes, keep it" --> ADD["total = total + number"]
  Q -- "no" --> DROP["skip it"]
  ADD --> N["move to the next number"]
  style T fill:#3776ab,color:#fff
  style Q fill:#1e293b,color:#fff
An accumulator: scan every item, add only the ones that pass a test, carry the running total forward.
Exercise

Read carefully — what does this print? Type your prediction, then Check. (Each squared value on its own line.)

for i in range(3):
    print(i * i)
Exercise

Write sign(n) that returns 1 for positive numbers, -1 for negative ones, and 0 for zero. Use if / elif / else (or a final bare return).

def sign(n):
    # return 1, -1, or 0
    pass
Exercise

Write count_long(words, min_len) that returns how many strings in words are longer than min_len. Combine a for loop with an if — this is the accumulator pattern with a counter instead of a sum.

def count_long(words, min_len):
    count = 0
    # loop over words; add to count when a word is long enough
    return count
Exercise

Fix sum_evens so it returns the sum of the even numbers in numbers. (Hint: a number is even when n % 2 == 0.) Right now it adds every number, because the condition is missing.

def sum_evens(numbers):
    total = 0
    for n in numbers:
        total = total + n   # bug: adds every number
    return total
Exercise

Write max_of(numbers) that returns the largest number in a non-empty list — without using the built-in max(). Walk the list with a for loop and keep track of the biggest value seen so far.

def max_of(numbers):
    best = numbers[0]
    # loop over numbers; replace best when you find a bigger one
    return best

Recap

  • A branch picks one path: if, then elif for further tests, then a catch-all else. The first true condition wins, so test strictest-first.
  • Tell equality apart from assignment: == asks, = stores. Stitch conditions together with and, or, not.
  • A for loop fires once per item — range(n) yields indices stopping at n-1, a list yields its members, enumerate yields both at once.
  • An accumulator marries a loop and a condition into a running total that climbs only for items that pass.

Next you will fold decisions and loops into reusable functions, so a branch or a scan you write once can be launched from anywhere in your program.

Checkpoint quiz

What does range(4) produce?

Which condition is true only for even numbers?

You need a grade of A for 90+ and B for 80+. Why write if score >= 90 ... elif score >= 80 rather than two separate if statements?

Go deeper — technical resources