Defining Functions

The Course
January 8, 2026
4 min read

A function gives a name to a piece of behaviour so you can use it more than once, and so a reader can understand your program without reading every line of it.

Define, then call Python
def greet(name):
    return f"Hello, {name}!"

print(greet("Ada"))
print(greet("Alan"))

message = greet("Grace")
print(message.upper())

The anatomy: def, the name, the parameters in parentheses, a colon, then an indented body. return hands a value back to whoever called it.

return, not print

This distinction is worth getting right on day one of functions.

One is usable, one is not Python
def add_printing(a, b):
    print(a + b)          # shows the answer

def add_returning(a, b):
    return a + b          # hands the answer back

add_printing(2, 3)
result = add_printing(2, 3)
print("captured:", result)      # None - nothing was returned

result = add_returning(2, 3)
print("captured:", result)
print(add_returning(2, 3) * 10) # usable in a bigger expression

A function that prints can only ever put text on a screen. A function that returns can be used in arithmetic, stored, tested, and printed if you want to. Return values; print at the edges of your program.

Every function returns something

A function with no return returns None. That is why print(add_printing(2, 3)) shows None — you are printing the absence of a result.

Several parameters, and defaults

A parameter can have a default value, which makes it optional at the call site.

Defaults Python
def price_with_tax(amount, rate=0.2):
    return round(amount * (1 + rate), 2)

print(price_with_tax(100))          # uses the default rate
print(price_with_tax(100, 0.05))    # overrides it

def greet(name, greeting="Hello", punctuation="!"):
    return f"{greeting}, {name}{punctuation}"

print(greet("Ada"))
print(greet("Ada", "Welcome"))
print(greet("Ada", punctuation="?"))   # skip the middle one by naming

Parameters with defaults must come after those without — Python has no way to guess otherwise.

Never default to a list

def add_item(item, basket=[]): looks reasonable and is a genuine trap: the default list is created once, when the function is defined, and is shared by every call that uses it. Use basket=None and create a fresh list inside.

The shared-default trap Python
def broken(item, basket=[]):
    basket.append(item)
    return basket

print(broken("apple"))
print(broken("pear"))      # the apple is still there

def fixed(item, basket=None):
    if basket is None:
        basket = []
    basket.append(item)
    return basket

print(fixed("apple"))
print(fixed("pear"))

Docstrings

A string on the first line of a function is its documentation. Tools read it, help() prints it, and your future self will thank you.

Documenting a function Python
def body_mass_index(weight_kg, height_m):
    """Return the BMI for a weight in kilograms and a height in metres.

    The result is rounded to one decimal place.
    """
    return round(weight_kg / height_m ** 2, 1)

print(body_mass_index(70, 1.75))
help(body_mass_index)

Say what it returns and what the arguments mean. Do not restate the code.

Functions calling functions

Small functions that call each other is how programs stay readable as they grow.

Building up Python
def is_vowel(letter):
    return letter.lower() in "aeiou"

def count_vowels(text):
    count = 0
    for letter in text:
        if is_vowel(letter):
            count += 1
    return count

def describe(text):
    vowels = count_vowels(text)
    return f"{text!r} has {vowels} vowels out of {len(text)} characters"

print(describe("Programming"))
print(describe("rhythm"))

Each function does one thing and is testable on its own. {text!r} in the f-string means “show the repr” — the quoted, debugging-friendly form.

Exercise

A tiny statistics toolkit

Write three functions — mean(values), largest(values) and range_of(values) (the difference between the largest and smallest) — then use them to report on the list. Each must return, not print.

readings = [18, 21, 25, 19, 30, 22]

# Your code here
Show one solution
readings = [18, 21, 25, 19, 30, 22]

def mean(values):
    """Return the arithmetic mean of a non-empty sequence of numbers."""
    return sum(values) / len(values)

def largest(values):
    """Return the biggest value."""
    return max(values)

def range_of(values):
    """Return the spread between the largest and smallest values."""
    return max(values) - min(values)

print(f"Mean:    {mean(readings):.1f}")
print(f"Largest: {largest(readings)}")
print(f"Range:   {range_of(readings)}")

largest is a thin wrapper over max and would not be worth writing in real code — but naming the operations you care about is exactly how a domain vocabulary starts.

What you learned

  • def name(parameters): defines a function; calling it runs the body.
  • return sends a value back; without it you get None.
  • Return values rather than printing them, so callers can use the result.
  • Default parameters make arguments optional — never default to a mutable value.
  • A docstring documents what the function returns and what its arguments mean.
Last updated on January 8, 2026

Was this article helpful?

Your response is saved on this device.