Technology  /  Python

🐍 Python 78 guides · updated 2026

From first variable to OOP, generators, and real projects — the language that runs everything from data pipelines to AI agents, taught the practical way.

Python Function Return Values: Single, Multiple, and None by Default

Every Python function returns a value. If you do not write a return statement, the function returns None. If you write return without an expression, it also returns None. Understanding what a function returns — and how to use that return value effectively — is fundamental to writing clean, testable code.

The return Statement

return immediately exits the function and sends a value back to the caller.

def square(n):
return n * n
result = square(7)
print(result) # 49
# Use directly in expressions
print(square(3) + square(4)) # 25

The returned value can be assigned, used in an expression, passed to another function, or discarded. The choice is the caller’s.

Implicit None Return

Without return, a function returns None. This surprises many beginners who write a function that prints its result rather than returning it.

def bad_square(n):
print(n * n) # prints, but does not return
def good_square(n):
return n * n # returns the value
x = bad_square(5) # prints 25, but x is None
y = good_square(5) # y is 25
print(x) # None
print(y) # 25
# Chaining fails with print-based functions
result = good_square(good_square(2)) # good_square(4) = 16
# bad_square(bad_square(2)) would fail — inner returns None, outer fails

Functions that produce side effects (write to files, update databases, display output) may legitimately return None. But if a function calculates something, it should return the result rather than printing it, so the caller can decide what to do with it.

Early Returns: Cleaner Than Deep Nesting

Returning early simplifies complex logic by handling edge cases at the top of the function and letting the main path proceed without nesting.

# Deeply nested — hard to follow
def get_discount(user):
if user is not None:
if user.is_active:
if user.membership == "premium":
return 0.20
else:
return 0.05
else:
return 0.0
else:
return 0.0
# Early returns — linear and easy to read
def get_discount(user):
if user is None or not user.is_active:
return 0.0
if user.membership == "premium":
return 0.20
return 0.05

Early returns are a form of guard clause: each check at the top handles a special case and exits, leaving the function body for the normal case.

Returning Multiple Values

Python’s return can send back multiple values separated by commas. Python packs them into a tuple automatically.

def analyse_list(numbers):
if not numbers:
return None, None, None
return min(numbers), max(numbers), sum(numbers) / len(numbers)
minimum, maximum, average = analyse_list([5, 3, 8, 1, 9, 2])
print(minimum) # 1
print(maximum) # 9
print(average) # 4.666...
# Can also capture as a tuple
stats = analyse_list([10, 20, 30])
print(stats) # (10, 30, 20.0)
print(stats[0]) # 10

Tuple unpacking (minimum, maximum, average = ...) assigns each returned value to a separate variable in one step. The number of variables on the left must match the number of values returned.

Returning Named Tuples for Clarity

When a function returns many values, the caller has to remember what each position means. A named tuple or dataclass makes the return value self-documenting.

from collections import namedtuple
Stats = namedtuple("Stats", ["minimum", "maximum", "mean", "count"])
def describe(numbers):
if not numbers:
return Stats(None, None, None, 0)
return Stats(
minimum=min(numbers),
maximum=max(numbers),
mean=sum(numbers) / len(numbers),
count=len(numbers),
)
result = describe([5, 3, 8, 1, 9])
print(result.mean) # 5.2
print(result.count) # 5
print(result) # Stats(minimum=1, maximum=9, mean=5.2, count=5)

Named tuples are especially useful in public APIs where callers should not rely on positional order.

Returning Different Types

Sometimes a function might return different types depending on the situation. This is a design decision with trade-offs.

def find_first(items, predicate):
"""Return the first item matching predicate, or None if not found."""
for item in items:
if predicate(item):
return item
return None # explicit None signals "not found"
result = find_first([1, 3, 5, 6, 7], lambda n: n % 2 == 0)
if result is not None:
print(f"First even: {result}") # First even: 6

The alternative is raising an exception when nothing is found. Which approach is right depends on whether “not found” is an expected, normal outcome (return None) or an error (raise an exception).

return in Loops

return inside a loop exits the function entirely, not just the loop. This is commonly used for early-exit searches.

def first_duplicate(items):
seen = set()
for item in items:
if item in seen:
return item # found one — return immediately
seen.add(item)
return None # exhausted the list without finding a duplicate
print(first_duplicate([1, 2, 3, 2, 4])) # 2
print(first_duplicate([1, 2, 3, 4])) # None

This is more efficient than continuing to check after finding the answer.

The print vs return Distinction

One of the most common beginner confusions:

def compute(a, b):
print(a + b) # produces visible output — but not reusable
def calculate(a, b):
return a + b # produces a value — reusable
total = sum([calculate(1, 2), calculate(3, 4), calculate(5, 6)])
print(total) # 21
# compute() can't be used this way — it returns None

Use print when you are writing output for a human. Use return when the value needs to be used by code. Functions that mix both — compute and print — are harder to test and reuse.

Common Mistakes

Assigning the result of a function that returns None. If a function modifies state in place (like list.sort()), it returns None. Assigning the result is a common bug:

numbers = [3, 1, 4, 1, 5]
sorted_numbers = numbers.sort() # sort() returns None!
print(sorted_numbers) # None
# Fix: use sorted() which returns a new sorted list
sorted_numbers = sorted(numbers)

Multiple return values mismatched during unpacking. If a function returns three values and you assign to two variables, Python raises ValueError: too many values to unpack.

Returning before all variables are set. An early return can bypass code that initialises variables used later in the function.