CSC108H1/Unit 3
Unit 3 · Intro to Programming

Functions

A function is a named, tested, reusable piece of behaviour with a written contract. In this course the contract — the type annotation and the docstring — is graded as heavily as the code, because it is what makes the code usable by anyone else.

By the end you can
  • Write a function with type annotations and a docstring containing examples.
  • Explain the difference between a parameter and an argument.
  • Predict what a function returns, including when it returns None.
  • Say why a local variable is invisible outside its function.
Why it matters & where it comes from
Why it exists

Without functions, a program is one long script where every change risks breaking something unrelated. Functions draw boundaries: inside is an implementation you can change freely, outside is a contract others rely on.

In the real world

Every library you will ever import is a set of functions whose docstrings are the only thing you read. Writing yours properly is practice for the job, and in this course it is directly worth marks.

Story & open questions

The design-recipe approach used here — contract, examples, then body — comes from the How to Design Programs tradition, which UofT's intro sequence has drawn on for years. The claim is that most beginner difficulty is not syntax but not knowing what to write next.

Anatomy of a function

Example Everything that belongs
python
def area(width: float, height: float) -> float:
    """Return the area of a rectangle with the given width and height.

    >>> area(3.0, 4.0)
    12.0
    >>> area(0.0, 5.0)
    0.0
    """
    return width * height
def names the function; the parentheses hold the parameters.
The annotations : float and -> float state the type contract. Python does not enforce them, but the course requires them and they document intent.
The docstring starts with one imperative sentence beginning "Return", then gives examples in doctest format — runnable, as Unit 8 shows.
Definition

A parameter is the name in the definition; an argument is the value supplied at the call. In area(3.0, 4.0), width is a parameter and 3.0 is an argument.

Every function returns something

A function with no return returns None. So x = print('hi') sets x to None: print displays a value, it does not produce one. Confusing printing with returning is the most common beginner error in this course.

Example Print versus return
python
# wrong: caller gets None and cannot use the result
def double_bad(n: int) -> None:
    print(n * 2)

# right: caller decides whether to print, store, or reuse
def double(n: int) -> int:
    """Return twice n."""
    return n * 2

print(double(5) + 1)   # 11 - only possible with the returning version

Scope

Rule Local names stay local

Names bound inside a function exist only while it runs. They are not visible outside, and they do not clash with names elsewhere. Assigning to a name inside a function always creates or updates a local name, even if a global of the same name exists.

Example Two independent totals
python
total = 100

def add_up(xs: list) -> int:
    """Return the sum of xs."""
    total = 0          # a NEW local, unrelated to the global
    for x in xs:
        total = total + x
    return total

print(add_up([1, 2, 3]))   # 6
print(total)               # 100 - untouched

This is a feature. It means you can write a function without auditing the whole program for name collisions.

Avoid reaching for globals

Reading a global from inside a function works, and it is still a bad habit: the function's behaviour then depends on something not in its parameter list, so the contract is a lie. Pass what you need as an argument and return what you produce.

Designing a function

Rule The order to write it in
1. Name it and write the signature with types.
2. Write the one-sentence docstring: what does it return?
3. Write two or three examples, including an edge case — empty input, zero, a negative.
4. Only now write the body, aiming to satisfy the examples you just committed to.

Writing the examples first is not ceremony. It forces the ambiguous decisions — what does this do with an empty list? — before the code has hidden them.

Example The edge case shows up early
python
def average(nums: list) -> float:
    """Return the mean of nums, or 0.0 if nums is empty.

    >>> average([1, 2, 3])
    2.0
    >>> average([])
    0.0
    """
    if not nums:
        return 0.0
    return sum(nums) / len(nums)

Without the second example, the empty case is a ZeroDivisionError discovered by whoever calls it. Writing the example forced a decision and documented it.

Common pitfalls

• Printing instead of returning. The caller cannot do anything with what was printed.
• Forgetting that code after return never runs.
• Writing a docstring that describes the implementation rather than the result. Say what it returns, not how it loops.
• Mutable default arguments: def f(xs=[]) reuses the same list on every call. Use None as the default and create the list inside.

Practice

Predict each answer before running it. Original exercises written for this guide in the style of the course's weekly sets.

Practice 1 · What is x?

What does x hold?

python
def greet(name: str) -> None:
    print('Hi ' + name)

x = greet('Sam')
Answer
None. The function prints but does not return, so the call evaluates to None. "Hi Sam" appears on screen but is not captured.
Practice 2 · Unreachable code

What does this return for [1, 2, 3]?

python
def f(xs: list) -> int:
    for x in xs:
        return x
    return 0
Answer
1. The return fires on the first iteration, so the loop never continues. This is a common accident when a loop was meant to accumulate something.
Practice 3 · Scope

Does this print 5 or 10?

python
n = 5
def bump() -> None:
    n = 10
bump()
print(n)
Answer
5. The assignment inside bump creates a local n that disappears when the call ends.
Practice 4 · Write it

Write count_above(nums, k) returning how many items exceed k.

Solution
python
def count_above(nums: list, k: float) -> int:
    """Return how many values in nums are strictly greater than k.

    >>> count_above([1, 5, 9], 4)
    2
    >>> count_above([], 0)
    0
    """
    total = 0
    for x in nums:
        if x > k:
            total = total + 1
    return total
Practice 5 · Mutable default

Why does this accumulate across calls?

python
def add(item, bag=[]):
    bag.append(item)
    return bag
Answer
The default list is created once, when the def executes, and reused by every call that omits bag. Use bag=None and if bag is None: bag = [] inside.
Practice 6 · Docstring quality

Improve: """Loops through the list and adds."""

Answer
"""Return the sum of nums.""" — states the result, not the mechanism. The caller does not care that a loop was involved.
Practice 7 · Multiple returns

Return both the minimum and the maximum of a non-empty list.

Solution
python
def min_max(nums: list) -> tuple:
    """Return (smallest, largest) of non-empty nums.

    >>> min_max([3, 1, 4])
    (1, 4)
    """
    return (min(nums), max(nums))
Python returns one object; a tuple lets that one object carry several values, and the caller can unpack it with lo, hi = min_max(xs).
Practice 8 · Composition

Using double and area from this page, what is area(double(2), 3)?

Answer
12. The inner call is evaluated first, giving 4, then area(4, 3). Functions compose because they return.

Expanded topic checklist

Course coverage. This unit is part of the complete CSC108H1 topic map. It should cover the following items before you move on:

  • Definition and call. Parameters versus arguments, annotations, return.
  • Docstrings. One-sentence summary plus doctest examples including an edge case.
  • Scope. Local names, and why globals are a bad habit.
  • Design order. Contract and examples before body.
Textbook/source alignment

The calendar lists functions under "program structure". Docstring and annotation style is set by the course handout each term; follow that exactly, since it is mechanically checked on assignments.

Core idea map

A function packages behaviour behind a contract. Contracts make testing possible (Unit 8) and make the object methods of Unit 7 comprehensible, since a method is just a function that belongs to a type.

Study routine. Never write a function body before its docstring examples. If you cannot write an example, you do not yet know what the function should do.

Deep practice extensions

Use these after the main practice set. They push on definitions and edge cases rather than routine calculation.

Extension 1 · Default return

What does a function with no return give back?

Guide
None. Always — there is no such thing as returning nothing.
Extension 2 · Parameter versus argument

Distinguish them.

Guide
Parameter is the name in the def; argument is the value passed at the call.
Extension 3 · Why examples first

What does writing docstring examples before the body buy you?

Guide
It forces the edge-case decisions into the open while they are still cheap, and it gives you a runnable test the moment the body exists.
Extension 4 · Scope rule

What happens when you assign to a name inside a function?

Guide
You create or update a local name. Any global of the same name is untouched.