Command Palette

Search for a command to run...

Hectal
PHASE 5Beginner ~28 min· topic 1 of 10

Topic 5.1

Defining and Calling Functions

In one line

A function is a named block of code that you define once with def and run whenever you call it by name. It can receive values (parameters), and hand a value back to the caller with return; if it never reaches a return, it gives back None.

Think of it like this

A recipe card for "masala tea" stuck on the fridge. You don't rewrite the steps every morning; you say "make masala tea" and follow the card. If the card says "for N cups", you can make two cups today and five tomorrow with the same card. A function is a recipe card for code: the steps are written once, given a name, and followed whenever someone asks for it.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Function
A named block of code that you can run again and again by calling its name.
def
The keyword that starts a function definition. It stands for "define".
Call
To run a function by writing its name followed by round brackets, such as greet("Asha").
Parameter
A name in the def line that receives a value when the function runs.
Argument
The actual value you give a function in a call.
Body
The indented lines under the def line. They run each time the function is called.
return
Ends the function and hands a value back to the caller.
Return value
The value a call turns into. Without a return, it is None.
Docstring
A string on the first line of a function body that describes what the function does.

Step by step

01Define once, call many times

The def line gives the function a name and lists its parameters. The indented body is the recipe. Nothing in the body runs while Python is reading the def; Python only remembers it.

A call such as greet("Asha") jumps into the body with name set to "Asha", runs the lines, then comes back to where the call was.

main.pywhole filepython
def greet(name):
    return "Hello, " + name + "!"


print(greet("Asha"))
print(greet("Ravi"))
Define once, call many timesdiagram
Rendering diagram…

02Parameters and arguments

name is the parameter: it exists only while the function runs, and it is a fresh variable on every call. Calling greet("Ravi") does not change the earlier call; each call gets its own name.

The order of arguments matters when there is more than one parameter: Python matches them left to right. Topic 5.2 shows the other ways to match them.

main.pywhole filepython
def rectangle_area(width, height):
    return width * height


print(rectangle_area(3, 4))   # width = 3, height = 4
print(rectangle_area(4, 3))   # same answer, but the roles swapped

03return hands the answer back

After return, the function stops. Any line below it in the same path never runs. The call becomes the returned value, so you can store it, print it, or use it inside a bigger expression.

To send back several values, list them with commas. Python packs them into a tuple, and you can unpack the tuple into separate names at the call.

main.pywhole filepython
def rectangle(width, height):
    area = width * height
    perimeter = 2 * (width + height)
    return area, perimeter      # one tuple with two values


a, p = rectangle(3, 4)          # unpacking
print(a, p)                     # 12 14

04No return means None

If the body finishes without hitting a return, Python quietly returns None. The same is true for a function that only prints. Many bugs come from using the result of such a function: total = show(5) + 1 fails because None + 1 is not allowed.

A function can also have several return lines on different paths, such as inside an if or a loop. The first one reached ends the call. If no path reaches a return, the answer is None.

main.pywhole filepython
def first_even(numbers):
    for n in numbers:
        if n % 2 == 0:
            return n        # leaves the function at once
    # if the loop ends, we fall off the end -> None


print(first_even([5, 7, 8, 10]))   # 8
print(first_even([5, 7, 9]))       # None

05Docstrings: a note for the next reader

A string literal as the first statement of the body becomes the function's docstring. It does not run as code; Python stores it on the function as __doc__. It is the standard place to say what the function does, and help(greet) prints it.

Write the docstring as a short command: "Return a greeting for the given name." Not "This function returns...".

main.pywhole filepython
def greet(name):
    """Return a greeting for the given name."""
    return "Hello, " + name + "!"


print(greet.__doc__)

06What Python does at def and at call

When Python reaches def greet(name): ... it builds a function object (the code of the body plus the parameter list, stored in memory like any other value) and binds the name greet to it, exactly as x = 5 binds x to a number. That is why you can later rebind the name, or assign the function to another name. Topic 5.6 builds on this idea.

At a call, Python creates a new frame: a small private area holding the parameters and any variables created in the body. When the function returns, the frame is thrown away, and the values in it vanish unless they were returned. This is why variables made inside a function are not visible outside (Topic 5.5).

What Python does at def and at calldiagram
Rendering diagram…

07Calling with the wrong number of arguments

Python checks the call against the parameter list before running the body. Too few or too many arguments is a TypeError that names the function and the missing parameter. The check happens at the call, not inside the function, so the error points at the line that made the call.

Try it yourself

  1. 1

    Predict, then run

    In the first example, say out loud what show_greeting("Ravi") returns before you press Run. Then check the line that prints result.

  2. 2

    Add a second parameter

    Change greet to def greet(name, greeting): and return greeting + ", " + name + "!". Update both calls so they pass two values, for example greet("Asha", "Namaste"). Then remove the second value from one call. What happens? Read the error message carefully.

  3. 3

    Return early

    In first_even, add print("checking", n) as the first line inside the loop. Run it with [5, 7, 8, 10]. Notice that 10 is never checked: return left the function at 8.

Code & diagrams

return versus print New tab

greet gives back a value. show_greeting only prints, so what it returns is None.

Sign in to run this example in your browser.

Expected output

greet returned: Hello, Asha!
Hello, Ravi!
show_greeting returned: None
Docstring: Return a greeting for the given name.
Several return values and falling off the end New tab

rectangle returns a tuple that is unpacked into two names. first_even returns None when no even number exists.

Sign in to run this example in your browser.

Expected output

area: 12 perimeter: 14
both together: (12, 14)
8
None
None
The life of a calldiagram
Rendering diagram…

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Forget an argument

Call greet() with nothing between the brackets.

main.pywhole filepython
def greet(name):
    return "Hello, " + name + "!"


print(greet())
terminal
$ python main.py
── what you'll see ──
Traceback (most recent call last):
File "main.py", line 5, in <module>
print(greet())
~~~~~^^
TypeError: greet() missing 1 required positional argument: 'name'

Break #2

Use the result of a function that only prints

Add 10 to the value returned by a function that has no return.

main.pywhole filepython
def add(a, b):
    print(a + b)


total = add(2, 3) + 10
print(total)
terminal
$ python main.py
── what you'll see ──
5
Traceback (most recent call last):
File "main.py", line 5, in <module>
total = add(2, 3) + 10
~~~~~~~~~~^~~~
TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'

Myth vs fact

Myth

Defining a function runs it.

Fact

def only stores the function. The body runs when you call it. A function that is never called never runs.

Myth

A function that prints a value has returned that value.

Fact

print sends text to the screen. The function's return value is None unless it uses return.

Myth

Parameters and arguments are two names for the same thing.

Fact

Parameters are the names in the def line. Arguments are the values passed in a call. Each call binds its arguments to the parameters.

Myth

A function can return only one value.

Fact

It returns exactly one object, but that object can be a tuple (or list, or dict) holding many values, which you can unpack at the call.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    A function is an ordinary object (type function). def creates it at run time and binds a name to it, so the same name can be bound again later, and functions can be stored in lists and dictionaries (Topic 5.6). In CPython each call gets its own frame (since 3.11 it lives on a cheap per-thread stack, and a full frame object is built only if something such as a traceback or debugger asks for it). Very deep call chains are limited by the recursion limit (Topic 5.8). The same def run twice makes two separate function objects that share one code object, which is why a closure factory (Topic 5.7) can hand out many functions cheaply.

  • ▸

    Calling a Python function is not free: each call costs roughly the time of a few simple operations. In a hot loop of millions of iterations, moving a tiny function inline can matter. For almost everything else, clarity beats micro-speed. Measure before you inline.

  • ▸

    Prefer functions that return their result over functions that print it. A returning function can be tested with assert add(2, 3) == 5, reused in a web response, logged, or piped into another function. A printing function can only be read by a human.

  • ▸

    In AI work, a plain function with a clear name, typed parameters and a docstring is also the unit a model can be allowed to call ("tool use"). The name and docstring become the instructions the model sees. Topic 16.10 builds this.

Remember this

  1. 1

    You create a function with the word def (short for *define*), a name, round brackets and a colon. The indented lines below are its body. Defining a function does not run it. It only stores the recipe under that name. The body runs each time you call the function by writing its name followed by round brackets, such as greet("Asha").

  2. 2

    The names inside the brackets of the def line are parameters: empty slots that the function fills in each time it runs. The actual values you supply in a call are arguments. In def greet(name): the word name is a parameter; in greet("Asha") the text "Asha" is an argument. People mix these words up in conversation, but it helps to keep them apart: parameters are in the recipe, arguments are the ingredients you bring today.

  3. 3

    return ends the function immediately and hands a value back to whoever called it. The call itself then becomes that value, so greet("Asha") + "!!" works like "Hello, Asha!" + "!!". A return can send back any value, including a tuple of several values, which Python's unpacking (Topic 4.3) then splits into separate names.

  4. 4

    A function that reaches the end of its body without meeting a return still gives something back: the special value None (Topic 1.4). The same happens for a bare return. This surprises beginners most often with print: a function that only prints shows text on the screen but returns None, so you cannot use its "answer" in further calculations. Printing is for people; returning is for the rest of the program.

  5. 5

    The first string in a function's body, if it is a plain string on its own line, is its docstring: a note that says what the function does. Python stores it in function.__doc__, and tools such as help() and editors show it. Write a one-line docstring for every function you share with others (Topic 5.9 covers styles in depth).

  6. 6

    Why bother? Functions remove copy-paste (fix a bug once, not in ten places), give a group of steps a clear name that explains what it is for, let you test small pieces separately, and keep each part small enough to hold in your head. In AI work, a function with a clear name, typed parameters and a docstring is exactly what an LLM (a large language model, the kind of AI behind chat assistants) is shown when it is allowed to "call a tool", which means asking your program to run that function. You will build this in Topic 16.10.

Explain it without notes

01

What is the difference between a parameter and an argument?

02

What does a function return if it has no return statement? Why does that matter?

03

What happens when Python reaches a def statement, and what happens when the function is called?

04

Why prefer return over print inside a function that computes something?

05

Is Python pass-by-value or pass-by-reference?

Practice

01

Write a function square(n) that returns n * n. Print square(7).

02

Write a function min_max(numbers) that returns the smallest and largest items of a list as two values. Unpack the result into low and high and print both.

03

Write a function describe(name, age) that returns the text Asha is 12 years old. for describe("Asha", 12). Print the result of calling it with your own values.

Trade-offs

  • ↔

    Many small functions versus one long script. Small functions are easy to name, test and reuse, but too many tiny ones scatter the logic and make the reader jump around. Split where a piece has a clear name and a clear job.

  • ↔

    Returning a tuple versus several functions. Returning (area, perimeter) is convenient, but callers must remember the order. For more than three or four values, a dictionary or a small class (Phase 8) is clearer.

Done when you can

  • Done when you can write a function with def, parameters and a return, and call it.

  • Done when you can explain parameters versus arguments with an example.

  • Done when you can say what a function without return gives back, and why that causes bugs.

  • Done when you can return two values and unpack them at the call.

  • Done when you can read a missing 1 required positional argument error and fix the call.