PythonReturn Values

Return Values

The return statement is how a function hands a result back to whoever called it. Python is flexible about what — and how many things — a function can return, which is powerful but also means it's worth learning the common patterns and pitfalls.

Returning a Single Value

The simplest case: compute something and send it back with return.

Python
def square(n):
    return n * n


result = square(7)
print(result)
49
Returning Multiple Values via a Tuple

Python doesn't have true "multiple return values" — instead, when you write return a, b, c, Python packs them into a tuple. The caller can then unpack that tuple into separate variables.

Python
def min_max(numbers):
    return min(numbers), max(numbers)


lowest, highest = min_max([4, 8, 15, 16, 23, 42])
print(lowest, highest)

# Or keep the tuple as-is
result = min_max([4, 8, 15])
print(result)
print(type(result))
4 42
(4, 15)
<class 'tuple'>
Returning Nothing (Implicit `None`)

A function with no return statement — or a bare return — hands back None. This is appropriate for functions whose job is to perform an action (print, mutate, save) rather than compute a value.

Python
def save_to_log(message):
    print(f"Saving: {message}")
    # nothing to give back — this function performs an action


outcome = save_to_log("User logged in")
print(outcome)
Saving: User logged in
None
Early Returns for Guard Clauses

Returning early — as soon as you know the answer, or as soon as an invalid case is detected — keeps functions flat and avoids deeply nested if/else chains.

Python
def get_discounted_price(price, is_member):
    if price < 0:
        return None  # guard clause: invalid input

    if not is_member:
        return price  # early return: no discount to apply

    return price * 0.9  # main logic, reached only if both checks pass


print(get_discounted_price(100, True))
print(get_discounted_price(100, False))
print(get_discounted_price(-5, True))
90.0
100
None

Compare this to the nested alternative, which does the same thing but is harder to follow as more conditions are added:

Python
def get_discounted_price_nested(price, is_member):
    if price >= 0:
        if is_member:
            return price * 0.9
        else:
            return price
    else:
        return None
Tip
Guard clauses handle edge cases and errors first and get them out of the way, so the rest of the function can assume "happy path" conditions already hold.
Returning Different Types Conditionally

Python won't stop you from returning a string in one branch and a number in another, but doing so usually makes a function harder to use correctly, since every caller has to check what type came back.

Python
def find_user(user_id, users):
    for user in users:
        if user["id"] == user_id:
            return user
    return "User not found"  # inconsistent: dict in one branch, str in another


users = [{"id": 1, "name": "Ada"}]
result = find_user(2, users)
if isinstance(result, str):
    print(result)
else:
    print(result["name"])
User not found
Warning
Mixing return types (a `dict` in one branch, a `str` in another) is a common code smell. Callers must add type checks to handle every possible shape, which is error-prone and easy to forget. Prefer returning `None` for the "not found" case, or raising an exception, so the type of a successful return value stays consistent.

Python
def find_user(user_id, users):
    for user in users:
        if user["id"] == user_id:
            return user
    return None  # consistent: either a dict, or None


result = find_user(2, users)
if result is None:
    print("User not found")
else:
    print(result["name"])
User not found
Returning a Function

Because functions are first-class objects in Python, a function can return another function. This is the foundation of closures and decorators, which you'll explore in depth in upcoming lessons.

Python
def make_multiplier(factor):
    def multiply(n):
        return n * factor
    return multiply  # returning a function, not calling it


double = make_multiplier(2)
triple = make_multiplier(3)

print(double(5))   # 10
print(triple(5))   # 15
10
15
Note
`make_multiplier` returns the inner `multiply` function itself (no parentheses after it) — each call produces a new, independent function that "remembers" its own `factor`. This memory of enclosing values is called a closure, covered next.
Quick Reference

Pattern

Example

Use case

Single value

return total

Most common case

Multiple values

return name, age

Packed into a tuple, unpacked by the caller

Nothing

return or no return

Action-oriented functions

Early return

if invalid: return None

Guard clauses, avoid deep nesting

Returning a function

return inner_func

Closures, decorators, factories

Example
Splitting a full name into first and last parts, returned as a tuple, with an early return guard for missing input.

Python
def split_name(full_name):
    if not full_name:
        return None, None

    parts = full_name.strip().split(" ", 1)
    if len(parts) == 1:
        return parts[0], ""
    return parts[0], parts[1]


first, last = split_name("Ada Lovelace")
print(first, "|", last)
print(split_name(""))
Ada | Lovelace
(None, None)