← Back to Functions · ← Previous: Parameters and Arguments
Chapter 01 named behavior. Chapter 02 let callers send values into that behavior. This chapter completes the first data round trip:
caller → arguments → function → return value → caller
Estimated study time: 75–100 minutes.
By the end of this chapter, you should be able to:
- write
return expression; - explain that
returnends the current function call; - store and reuse returned values;
- distinguish
print()fromreturn; - use returned values in expressions and conditions;
- return ordinary Python values, tuples, and
None; - use different return statements on different branches;
- distinguish
returnfrombreak; - trace input, transformation, return, and caller-side use.
def double(number):
return number * 2
result = double(6)
print(result)Output:
12
Trace:
6 binds to number
→ number * 2 becomes 12
→ return sends 12 back
→ double(6) becomes 12
→ result receives 12
A function does not assign directly to a caller variable. It returns a value, and the caller decides what happens next.
def square(number):
return number * number
answer = square(5)
print(answer)Output:
25
Think:
square(5) → 25
Because the call produces a value, it can participate in another expression:
def double(number):
return number * 2
final_score = double(7) + 3
print(final_score)Output:
17
def show_total(price, quantity):
print(price * quantity)
def calculate_total(price, quantity):
return price * quantityThe first function displays a value. The second sends a value to its caller.
print(...) → display something
return ... → send a value to the caller
A calculation is usually more reusable when the function returns the result and the caller chooses whether to print, compare, store, or combine it.
def calculate_total(price, quantity):
return price * quantity
total = calculate_total(8, 3)
print(total)
print(calculate_total(5, 4))Output:
24
20
A well-named intermediate variable is often easier to trace while learning or debugging.
def get_status():
return "ready"
def is_passing(score):
return score >= 60
def get_topics():
return ["strings", "loops", "functions"]A return value can be a string, number, Boolean, collection, tuple, None, or another ordinary Python value.
def is_passing(score):
return score >= 60
if is_passing(75):
print("Passed")Output:
Passed
is_passing(75) evaluates to True, so the Boolean and if rules learned earlier still apply.
def get_message():
return "Ready"
print("This line never runs")When return runs:
evaluate expression
→ obtain value
→ leave function
→ continue at caller
Necessary work should not appear after an unconditional return in the same path.
def classify_score(score):
if score >= 90:
return "excellent"
if score >= 60:
return "passing"
return "needs review"Calls:
print(classify_score(95))
print(classify_score(72))
print(classify_score(40))Output:
excellent
passing
needs review
Only one return statement runs per call. Once one runs, the current call is finished.
def describe_quantity(quantity):
if quantity <= 0:
return "invalid quantity"
return "quantity accepted"The special case exits first, leaving the normal path easy to read. Use early returns when they improve clarity.
def find_first_even(numbers):
for number in numbers:
if number % 2 == 0:
return number
return Noneprint(find_first_even([3, 7, 8, 10]))Output:
8
return number exits the function, not only the loop.
break → leave the current loop
return → leave the current function call
break can continue with later statements in the same function. return transfers control back to the caller.
def show_ready():
print("Ready")
result = show_ready()
print(result)Output:
Ready
None
If execution reaches the end without an explicit return, the call result is None.
def show_if_nonnegative(number):
if number < 0:
return
print(number)Bare return exits immediately and produces None.
These can all produce None:
reach end of function → None
bare return → None
return None → None
An explicit return None can communicate intent:
def find_positive(numbers):
for number in numbers:
if number > 0:
return number
return NoneHere None means that no positive value was found.
def is_empty(items):
return len(items) == 0This function returns a Boolean. A search function may return None to mean “not found.”
Both values are falsy in Boolean contexts, but they do not mean the same thing. When the distinction matters, test deliberately.
def calculate_area(width, height):
return width * heightFor calculate_area(4, 6):
evaluate width * height
→ obtain 24
→ return 24
→ leave function
The resulting value becomes the value of the call expression.
def get_even_numbers(numbers):
evens = []
for number in numbers:
if number % 2 == 0:
evens.append(number)
return evensresult = get_even_numbers([1, 2, 3, 4, 5, 6])
print(result)Output:
[2, 4, 6]
Detailed object ownership and mutation design come later.
def get_dimensions():
return 1920, 1080
dimensions = get_dimensions()
print(dimensions)Output:
(1920, 1080)
The function returns one tuple. Because tuple unpacking is already familiar:
width, height = get_dimensions()
print(width)
print(height)Output:
1920
1080
It is one returned tuple, not two independent return values.
def calculate_total(price, quantity):
print(price * quantity)
total = calculate_total(8, 3)
print(total)Output:
24
None
The function displayed 24, but the call result is None.
Fix:
def calculate_total(price, quantity):
return price * quantityIncorrect for counting every even number:
def count_even(numbers):
count = 0
for number in numbers:
if number % 2 == 0:
count += 1
return countThe function exits on the first iteration.
Correct:
def count_even(numbers):
count = 0
for number in numbers:
if number % 2 == 0:
count += 1
return countIndentation changes when the function exits.
def get_level(score):
if score >= 90:
return "high"
if score >= 60:
return "medium"Scores below 60 implicitly return None.
If every score should have a category:
def get_level(score):
if score >= 90:
return "high"
if score >= 60:
return "medium"
return "low"Design the possible results deliberately.
def calculate_total(price, quantity):
return price * quantity
total = calculate_total(12, 4)caller has 12 and 4
↓
arguments bind to price and quantity
↓
function evaluates price * quantity
↓
result is 48
↓
return sends 48 back
↓
call expression becomes 48
↓
total receives 48
This is the main mental model of the chapter.
File: examples/calculate_total.py
def calculate_total(price, quantity):
return price * quantity
total = calculate_total(12, 4)
print(total)
print(total + 5)Expected output:
48
53
File: examples/classify_score.py
def classify_score(score):
if score >= 90:
return "excellent"
if score >= 60:
return "passing"
return "needs review"
print(classify_score(95))
print(classify_score(72))
print(classify_score(40))Expected output:
excellent
passing
needs review
File: examples/find_first_even.py
def find_first_even(numbers):
for number in numbers:
if number % 2 == 0:
return number
return None
print(find_first_even([3, 7, 8, 10]))
print(find_first_even([1, 3, 5]))Expected output:
8
None
Create classify_temperature(temperature).
Requirements:
- return
"hot"for values at least30; - return
"mild"for values at least18but below30; - return
"cold"otherwise; - call it with
34,22, and10; - store each result before printing it.
Expected output:
hot
mild
cold
Do not use type hints, defaults, *args, or **kwargs.
Before continuing, confirm that you can:
- write
return expression; - explain that the expression is evaluated before leaving the function;
- store and reuse returned values;
- use a returned Boolean in
if; - distinguish
print()fromreturn; - use different returns on different branches;
- distinguish
returnfrombreak; - explain implicit
None, barereturn, andreturn None; - explain that
return a, breturns one tuple; - recognize a return placed too early in a loop;
- trace values from arguments back to the caller.
| Need | Form | Meaning |
|---|---|---|
| return value | return expression |
evaluate, leave function, send value to caller |
| store result | result = function() |
bind returned value in caller |
| use result | print(function()) |
use returned value in another call |
| return Boolean | return condition |
caller receives True or False |
return None |
return / return None |
leave function with None |
implicit None |
reach end | call result is None |
| return tuple | return a, b |
return one tuple |
| stop loop | break |
leave current loop |
| stop function | return value |
leave current function call |
This chapter intentionally defers:
- local/global scope rules;
- type hints and return annotations;
- default values;
*argsand**kwargs;- positional-only and keyword-only syntax;
- argument unpacking;
- nested functions and lambdas;
- decorators, generators,
yield, and recursion; - exception handling;
- advanced ownership and mutation design.
You can now trace:
caller → arguments → parameters → function body → return value → caller
The next question is:
Where do names inside and outside a function exist, and when are they visible?
That leads to Chapter 04: Scope.
Return to the Functions learning path or the full learning path.
Primary Python documentation: