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.