Return values from functions are how a function communicates results back to the caller. Using the return keyword, you can send back single values, multiple values (as tuples), complex objects, or nothing at all (None). In 2026, good functions return clear, predictable values — often with type hints and docstrings — making code easier to read, test, and reuse.
Here’s a complete, practical guide to returning values: single returns, multiple returns, early returns, implicit None, and modern patterns.
1. Basic Return (Single Value)
def add_numbers(x: int, y: int) -> int:
"""Return the sum of two integers."""
return x + y
result = add_numbers(3, 4)
print(result) # 7
2. Returning Multiple Values (Tuple Unpacking)
def divide_numbers(x: float, y: float) -> tuple[float, float]:
"""Return quotient and remainder."""
if y == 0:
raise ValueError("Division by zero")
quotient = x / y
remainder = x % y
return quotient, remainder # implicit tuple
# Unpack the returned tuple
q, r = divide_numbers(10, 3)
print(f"Quotient: {q}, Remainder: {r}") # Quotient: 3.333..., Remainder: 1.0
# Or receive as single tuple
result = divide_numbers(10, 3)
print(result) # (3.333..., 1.0)
3. Early Return & Conditional Returns
def check_number(x: float) -> str:
"""Classify number as Negative, Zero, or Positive."""
if x < 0:
return "Negative" # early return — exits function
if x == 0:
return "Zero"
return "Positive" # implicit else
print(check_number(-5)) # Negative
print(check_number(0)) # Zero
print(check_number(7)) # Positive
4. Returning Nothing (Implicit/Explicit None)
def print_welcome(name: str) -> None:
"""Print greeting — returns None implicitly."""
print(f"Welcome, {name}!")
result = print_welcome("Alice") # Welcome, Alice!
print(result) # None
# Explicit None return (useful for clarity)
def update_record(record_id: int) -> None:
"""Update database record — no return value needed."""
# ... database logic ...
return None
5. Real-World Pattern: Return Complex Objects
from typing import TypedDict
class OrderResult(TypedDict):
total: float
items_count: int
status: str
def process_order(items: list[float], discount: float = 0.0) -> OrderResult:
"""Process order and return structured result."""
subtotal = sum(items)
total = subtotal * (1 - discount)
return {
"total": round(total, 2),
"items_count": len(items),
"status": "success" if total > 0 else "empty"
}
order = process_order([100, 50, 25], discount=0.1)
print(order)
# {'total': 157.5, 'items_count': 3, 'status': 'success'}
Best Practices & Common Patterns (2026 Edition)
- Type hints — always use them (
-> int,-> tuple[float, float],-> None) — improves readability and IDE/mypy support - Docstrings — document what is returned (especially for complex types)
- Single return vs. early returns — prefer early returns for guard clauses (validation, errors); single return for simple logic
- Multiple returns — return tuples for 2–3 values; use dicts/classes for >3 or named results
- Avoid returning mutable defaults — if returning lists/dicts, create new ones inside function
- Pitfall — forgetting
return? function returnsNonesilently - Modern tip — use
TypedDictordataclassesfor structured returns — better than plain dicts
Conclusion
Return values are how functions deliver results — single values for simple tasks, tuples for multiple outputs, None for side-effect-only functions, or structured objects for complex logic. In 2026, use type hints, clear docstrings, early returns for clarity, and tuple unpacking or dicts for multiple results. Thoughtful return design makes functions predictable, testable, and easy to use — the hallmark of great code.
Next time you write a function — decide what it should return before you write the body. A clear return makes the function’s purpose obvious to everyone who reads it.