Skip to main content

Functions: naming a chunk of work

Imagine writing out the same five steps for making tea every single time you wanted a cup, instead of just saying "make tea." A function is the programming version of that shortcut: a named, reusable recipe — a little sub-program inside your program. You describe some work once, give it a name, and from then on you run that whole block of work just by saying its name. The work itself is written in one place; the name is how you trigger it.

There are two separate moments in a function's life, and keeping them apart is the key to everything that follows. First you define the function — you write down the recipe. Defining does not run the steps; it only records them under a name for later. Second, you call the function — you actually ask it to run. You can define a function once and call it a thousand times.

# DEFINE the recipe (this does not run the body yet)
def greet(name):
print("Hi, " + name)

# CALL it — now the body actually runs
greet("Sam") # prints: Hi, Sam

Let us read that def line one piece at a time, because every symbol earns its place:

  • def is a keyword — a word Python reserves for a special meaning. Here it announces "I am defining a function."
  • greet is the name you choose. You will use this name later to call the function. Pick names that say what the function does.
  • (name) is the list of parameters — the inputs the function expects. More on this word in a moment.
  • The : colon and the indentation (the spaces at the start of the next lines) mark the body — the steps that run when the function is called. Indentation is how Python knows which lines belong inside the function.
note

Just like with loops and if, the colon plus indentation mark the body. The body does not run when you define the function — defining only files the recipe away. It runs fresh every single time you call the function, using whatever inputs you hand it that time.

When you call a function, something specific happens to the order things run in — the flow of control, which just means "which line runs next." Normally Python reads top to bottom. But a function call is a detour: execution jumps into the function, runs its body line by line, and then comes back to exactly where it left off, carrying any result with it.

print("before") # 1. runs first -> before
greet("Sam") # 2. jump IN, run the body -> Hi, Sam, then come back
print("after") # 3. continues here -> after
# Output, in order:
# before
# Hi, Sam
# after

Now the two words that sound alike and trip up nearly everyone: parameter versus argument. A parameter is the placeholder name written in the def line — it stands for an input the function will receive, but it has no value yet. An argument is the actual value you hand over at the moment you call the function. The argument gets poured into the parameter.

def greet(name): # "name" is the PARAMETER (a placeholder)
print("Hi, " + name)

greet("Sam") # "Sam" is the ARGUMENT (the real value)
greet("Lee") # same parameter, different argument -> Hi, Lee
tip

One sentence to keep forever: the parameter is the empty cup named in the recipe; the argument is the tea you actually pour in when you make the order. In greet("Sam"), name is the parameter and "Sam" is the argument.

So far our function only does something (prints). But the most useful functions return a value — they compute an answer and hand it back to whoever called them, so the caller can store it in a variable and use it later. The return keyword is how a function says "here is my answer; take it."

def add(a, b):
return a + b # hand the sum back to the caller

result = add(2, 3) # the returned value (5) flows into result
print(result) # prints 5
print(add(10, 4) * 2) # 14 * 2 -> prints 28 (you can use the result right away)

Here is the single biggest beginner confusion, laid out side by side. return hands a value back to the program. print only shows text on the screen for a human to read — it hands nothing back. They look similar because both "produce" the number 5 in the example below, but only one gives you something you can keep working with.

# Version A: RETURNS the sum
def add_return(a, b):
return a + b

# Version B: PRINTS the sum
def add_print(a, b):
print(a + b)

r1 = add_return(2, 3) # nothing on screen yet; r1 holds 5
print(r1 + 1) # 6 -> we can do math with the returned value

r2 = add_print(2, 3) # screen shows 5 immediately... but
print(r2) # None -> add_print returned nothing!
# print(r2 + 1) would CRASH: you cannot add 1 to None
tip

result = add(2, 3) only works because add uses return. If it used print instead, you would see 5 flash on the screen, but result would hold Python's empty value None — useless for further math or storing. Rule of thumb: use print to show a human something; use return to give a value back to the rest of your program.

What if a function has no return at all? Then it still hands something back — the special empty value None, Python's way of saying "nothing here." Every function returns something; without an explicit return, that something is None.

def shout(word):
print(word.upper()) # does work, but never returns a value

answer = shout("hi") # screen shows: HI
print(answer) # None (no return -> None comes back)

Functions can take more than one parameter — just separate them with commas, and pass the arguments in the same order. You can also give a parameter a default value, used automatically when the caller leaves that argument out:

def greet(name, greeting="Hi"): # greeting defaults to "Hi"
return greeting + ", " + name

print(greet("Sam")) # Hi, Sam (default used)
print(greet("Sam", "Hello")) # Hello, Sam (default overridden)

A return also ends the function immediately — the moment Python hits it, the function stops and hands back that value, ignoring any lines below. This lets you return early, which is handy for quick exits:

def describe(n):
if n == 0:
return "zero" # exits right here if n is 0
if n > 0:
return "positive"
return "negative" # only reached if n is below 0

print(describe(0)) # zero
print(describe(5)) # positive

Why bother with functions at all? Three big payoffs:

  • Avoid repetition (DRY). DRY stands for "Don't Repeat Yourself." Write the logic once, call it everywhere. When a bug appears, you fix it in one place instead of in ten copies scattered across your program.
  • Name an idea. A well-named function like add or greet tells a reader what is happening without forcing them to read every line of how. The name becomes a label for a whole idea.
  • Test pieces in isolation. A small function with clear inputs and one clear output is easy to check on its own — feed it a value, confirm the returned answer.

One more idea that surprises beginners: scope. Scope means "where a variable exists and can be used." A variable created inside a function is local — it lives only inside that function, like a scratch note that gets thrown away the instant the function finishes. The outside world never sees it, and trying to use it outside is an error.

def make_total():
subtotal = 10 # LOCAL: exists only inside make_total
return subtotal

make_total()
print(subtotal) # ERROR: NameError - subtotal is not defined out here
note

The fix is exactly what return is for: if you want a value from inside a function, return it and catch it on the outside, like total = make_total(). (A function can read variables defined outside it, but the safe, predictable habit is to pass inputs in as arguments and hand results out with return — keep the inside and outside connected only through those two doors.)

Let us pull it all together with a few small, complete functions. Read each call and its commented output:

# 1. add two numbers and return the sum
def add(a, b):
return a + b

print(add(2, 3)) # 5

# 2. is a number even? return True or False
def is_even(n):
return n % 2 == 0 # % is remainder; even numbers leave 0

print(is_even(4)) # True
print(is_even(7)) # False

# 3. build a greeting string and return it (return, not print)
def greet(name):
return "Hi, " + name

message = greet("Sam") # message holds the string "Hi, Sam"
print(message) # Hi, Sam

Notice how is_even returns a True/False answer you could feed straight into an if, and how greet returns a string we tucked into message for later use. That hand-back-a-value pattern is the whole point of return.

Why it matters

You now have the four ideas that make functions work: define a named recipe, call it with arguments that fill its parameters, let it return a value back to you, and remember that its inside variables are local. Next, in the lesson on lists, you will start handing functions whole collections of values to work over — which is where these small recipes turn into real programs.

Where this leads: functions are the units you test in isolation, the heart of writing reliable code. See Where this leads next.

Practice

Write a tiny function that returns a value — and run it live:

⌨️ Challenge — Python · practice (not graded)

Write max_of_two(a, b) that returns the larger of the two numbers. If they are equal, return either one.

Checkpoint

Required checkpoint

Functions: naming a chunk of work

Pass to unlock the Next button below

Next: Lists →