Unit 2: Conditionals and Iterations; Functions and Recursion

ECE181 — Introduction To Python 7 min read

Python executes statements top-to-bottom, but control-flow constructs let a program choose which statements run and how often. Everything in this unit rests on two ideas: a Boolean condition (an expression evaluating to True or False) and a block (a group of statements set off by consistent indentation, conventionally four spaces).

Governing conventions this unit relies on:

  • Truth values: every object has a truthiness — 0, 0.0, "", [], None are falsy; almost everything else is truthy.
  • Comparison operators: ==, !=, <, >, <=, >= return Booleans; chaining is allowed, e.g. 0 < x < 10.
  • Logical operators: and, or, not, evaluated with short-circuiting (a and b skips b if a is falsy).
  • Indentation as syntax: a colon : opens a block and the indented lines below it form the block; there are no braces.
  • Namespaces: names created inside a function are local and vanish when the call returns.

II. Conditional Execution — choosing which block runs

Conditionals branch execution on the value of a Boolean condition.

A. Conditional expressions

The compact one-line form that yields a value rather than executing a block.

  • Syntax: value_if_true if condition else value_if_false — a single expression, not a statement.
  • Use: assign or return one of two values without a full if block.
  • Example:
    PYTHON
      status = "adult" if age >= 18 else "minor"
  • Contrast with statements: a conditional expression produces a value; an if statement performs an action.

B. if statement

The one-way branch: run a block only when a condition holds.

  • Form: header if condition: followed by an indented body.
  • Behaviour: if the condition is falsy the whole body is skipped and control passes on.
    PYTHON
      if temperature > 100:
          print("boiling")
  • Empty body: use pass as a placeholder when a body is syntactically required but does nothing.

C. two way if - else

A binary decision where exactly one of two blocks always runs.

  • Form: an if header, its body, then an else: header and its body.
  • Guarantee: the two paths are mutually exclusive and jointly exhaustive.
    PYTHON
      if n % 2 == 0:
          print("even")
      else:
          print("odd")

D. nested if and multi-way if-elif-else statement

Two techniques for handling more than two outcomes.

  1. Nested if: an if/else placed inside the body of another, testing a second condition only after the first is settled.
    PYTHON
       if x > 0:
           if x > 100:
               print("large positive")
           else:
               print("small positive")
  2. Multi-way if-elif-else: a flat chain of conditions tested in order; the first truthy branch runs and the rest are skipped.
    PYTHON
       if score >= 90:
           grade = "A"
       elif score >= 80:
           grade = "B"
       else:
           grade = "C"
    • Preference: the elif chain is flatter and more readable than deep nesting when the tests are independent and sequential.

III. Iteration — repeating a block

Loops execute a block repeatedly until a stopping condition is met.

A. for loop

Iterates over the items of a sequence, binding each to a variable in turn.

  • Form: for item in iterable: — works over lists, strings, tuples, ranges.
  • Counting: range(start, stop, step) generates integers up to but excluding stop.
    PYTHON
      for i in range(1, 6):
          print(i)        # 1 2 3 4 5
  • Use: definite iteration, when the number of repetitions is known in advance.

B. while loop

Repeats a block as long as a condition stays truthy.

  • Form: while condition: — the condition is re-tested before each pass.
  • Risk: the body must eventually make the condition false, or the loop runs forever.
    PYTHON
      n = 5
      while n > 0:
          print(n)
          n -= 1        # update prevents infinite loop
  • Use: indefinite iteration, when repetitions depend on runtime events.

C. nested loops

A loop placed inside another; the inner loop completes fully for each pass of the outer.

  • Iteration count: an outer loop of m passes containing an inner loop of n passes runs the inner body m × n times.
    PYTHON
      for r in range(3):
          for c in range(3):
              print(r, c)   # prints 9 coordinate pairs
  • Use: grids, tables, and pairwise comparisons.

D. break and continue

Two statements that alter the normal flow of a loop.

  1. break: exits the enclosing loop immediately, skipping any remaining passes.
    PYTHON
       for x in data:
           if x < 0:
               break     # stop at first negative
  2. continue: skips the rest of the current pass and jumps to the next iteration.
    PYTHON
       for x in range(10):
           if x % 2:
               continue  # ignore odd numbers
           print(x)
    • Scope: both affect only the innermost loop that contains them.

IV. Random Numbers — controlled unpredictability

The random module produces pseudo-random values for simulation, sampling and games.

A. random numbers

Generating numbers whose sequence is unpredictable yet reproducible from a seed.

  • Import: import random before use.
  • Key functions:
    • random.random(): a float in the half-open interval [0.0, 1.0).
    • random.randint(a, b): an integer in [a, b], both endpoints included.
    • random.choice(seq): one item picked from a sequence.
    • random.seed(n): fixes the starting point so results repeat — useful for testing.
  • Pseudo-random nature: values come from a deterministic algorithm, not true randomness, so the same seed yields the same stream.
    PYTHON
      random.seed(1)
      print(random.randint(1, 6))   # a repeatable "dice roll"

V. Functions — packaging reusable behaviour

A function is a named, reusable block that optionally takes inputs and returns a result.

A. function calls

Invoking an existing function to run its body and obtain its value.

  • Form: name(arguments); parentheses are required even when empty.
  • Return value: the call expression evaluates to whatever the function returns (None if no return).
    PYTHON
      length = len("python")   # call returns 6

B. type conversion and coercion

Changing a value's type explicitly or letting Python do it automatically.

  1. Type conversion (explicit): built-in functions convert on demand — int("42"), float(3), str(10), bool(0).
  2. Coercion (implicit): in mixed arithmetic Python promotes to the wider type — 3 + 2.0 yields 5.0 (int coerced to float).
    • Failure: an invalid conversion raises ValueError, e.g. int("abc").

C. math functions

The math module supplies mathematical operations beyond the basic operators.

  • Import: import math.
  • Common functions: math.sqrt(x), math.pow(x, y), math.floor(x), math.ceil(x), math.log(x), math.sin(x).
  • Constants: math.pi, math.e.
    PYTHON
      math.sqrt(16)     # 4.0
      math.floor(3.9)   # 3

D. adding new function

Creating your own function with the def statement.

  • Form: def name(parameters): followed by an indented body; return sends a value back.
    PYTHON
      def area(radius):
          return math.pi * radius ** 2
  • Docstring: a string literal as the first line documents the function.
  • Composition: functions may call other functions, building larger behaviour from small pieces.

E. parameters and argument

The distinction between a function's declared inputs and the values supplied at a call.

  • Parameter: a name in the def header — a placeholder inside the function.
  • Argument: the actual value passed when calling.
  • Positional vs keyword: arguments match by order (greet("Sam")) or by name (greet(name="Sam")).
  • Default values: a parameter may specify a fallback — def greet(name="friend"): — used when the argument is omitted.

VI. Recursion — a function that calls itself

A. recursion and its use

Solving a problem by reducing it to a smaller instance of the same problem.

  • Two required parts:
    • Base case: a condition that stops recursion without a further call.
    • Recursive case: the function calls itself on a smaller input, moving toward the base case.
  • Mechanism: each call gets its own stack frame; frames unwind as base cases return.
    PYTHON
      def factorial(n):
          if n <= 1:          # base case
              return 1
          return n * factorial(n - 1)   # recursive case
  • Uses: naturally recursive structures — factorials, Fibonacci numbers, tree traversal, and divide-and-conquer algorithms.
  • Limitation: missing or unreachable base cases cause infinite recursion and a RecursionError when the call stack overflows; iterative solutions are often more memory-efficient for simple counting.