How to return multiple values in Python
Learn how to return multiple values in Python. Discover different methods, tips, real-world applications, and how to debug common errors.

Python functions can return multiple values, a common requirement for cleaner, more efficient code. The language provides several elegant, built-in ways to accomplish this without complex data structures.
You'll discover techniques, real-world application tips, and debugging advice. This helps you select the right approach and write more robust functions for your specific use case.
Using return to return multiple values
def get_user_info():
name = "Alice"
age = 30
is_admin = True
return name, age, is_admin
result = get_user_info()
print(result)
print(type(result))--OUTPUT--('Alice', 30, True)
<class 'tuple'>
The get_user_info function showcases Python's most direct method for returning multiple items. When you list variables like name, age, is_admin after the return keyword, Python automatically packs them into a tuple. You don't need to add parentheses; the commas alone signal the tuple's creation.
As the output shows, the result variable holds a single tuple object, ('Alice', 30, True). This is confirmed when type(result) returns <class 'tuple'>. This technique is a clean and lightweight way to group related data for a return without needing a more complex structure like a dictionary or a custom class.
Common techniques for returning multiple values
While returning a tuple is the default, you can also unpack those values directly or use a list or dict for more specialized needs.
Unpacking the returned tuple
def get_dimensions():
width = 1920
height = 1080
return width, height
width, height = get_dimensions()
print(f"Width: {width}")
print(f"Height: {height}")--OUTPUT--Width: 1920
Height: 1080
Instead of capturing the returned tuple in a single variable, you can unpack it directly into multiple variables. The line width, height = get_dimensions() demonstrates this—Python automatically assigns the first returned value to width and the second to height.
- This technique, known as tuple unpacking, requires the number of variables on the left to exactly match the number of values the function returns.
It’s a clean way to get immediate access to individual values without needing to index the tuple, making your code more readable.
Using list for variable number of return values
def get_prime_factors(n):
factors = []
d = 2
while n > 1:
while n % d == 0:
factors.append(d)
n //= d
d += 1
return factors
print(get_prime_factors(12))
print(get_prime_factors(42))--OUTPUT--[2, 2, 3]
[2, 3, 7]
When you don't know how many values a function will return, a list is an excellent choice. The get_prime_factors function illustrates this well. It dynamically builds a list of prime factors, and the list's final size depends entirely on the input number.
- Because lists are mutable, you can modify them inside the function—for example, by adding elements with
append. This makes them ideal for collecting results of an unknown quantity.
Using dict for named return values
def analyze_text(text):
return {
"length": len(text),
"words": len(text.split()),
"uppercase": sum(1 for c in text if c.isupper())
}
result = analyze_text("Hello World! Python is AMAZING.")
print(result["length"])
print(result["words"])--OUTPUT--31
4
Returning a dictionary is a great choice when you want to give each value a specific name. In the analyze_text function, each piece of data is paired with a descriptive key like "length" or "words".
- This makes your code self-documenting. You access values by a meaningful name, such as
result["words"], instead of by position. - It eliminates ambiguity, so you'll always know exactly what each value represents. This is especially helpful when a function returns many distinct items.
Advanced return value strategies
When you need more structure than a basic tuple or dict can offer, you can use advanced tools like namedtuple, custom classes, and the yield keyword.
Using namedtuple for structured returns
from collections import namedtuple
def get_stats(numbers):
Stats = namedtuple('Stats', ['mean', 'median', 'mode'])
mean = sum(numbers) / len(numbers)
median = sorted(numbers)[len(numbers) // 2]
mode = max(numbers, key=numbers.count)
return Stats(mean, median, mode)
stats = get_stats([1, 2, 2, 3, 4, 5])
print(stats.mean, stats.median, stats.mode)--OUTPUT--2.8333333333333335 3 2
A namedtuple from the collections module offers a middle ground between a tuple and a dictionary. It’s a way to create lightweight, tuple-like objects where you can access values by name, not just by index. In the get_stats function, a Stats namedtuple is defined with fields for mean, median, and mode, making the return value more descriptive.
- You can access data using dot notation, like
stats.mean, which is more readable than using an index. - It combines the memory efficiency of a tuple with the self-documenting nature of a dictionary.
Using custom class for complex return values
class QueryResult:
def __init__(self, data, count, page):
self.data = data
self.count = count
self.page = page
def has_next_page(self):
return len(self.data) == self.count
def search_database(query):
data = ["result1", "result2"]
return QueryResult(data, 2, 1)
result = search_database("python")
print(f"Results: {result.data}, Next page: {result.has_next_page()}")--OUTPUT--Results: ['result1', 'result2'], Next page: True
For complex scenarios where data and behavior are intertwined, a custom class is your best tool. The search_database function returns an instance of the QueryResult class, which doesn't just hold data—it also includes methods like has_next_page() that operate on that data.
- This approach bundles related attributes and functions into a single, organized object.
- It’s ideal for representing complex return values, such as API responses or database results, because it keeps the logic tied directly to the data it describes.
Using yield to return values incrementally
def fibonacci(n):
a, b = 0, 1
for _ in range(n):
yield a
a, b = b, a + b
for number in fibonacci(6):
print(number, end=" ")--OUTPUT--0 1 1 2 3 5
The yield keyword turns a function into a generator, which produces values one at a time. Unlike return, which terminates a function completely, yield sends back a value and pauses execution. When the next value is requested, the function resumes right where it left off, remembering its state.
- This makes generators incredibly memory-efficient. The
fibonaccifunction calculates one number on demand, making it perfect for large or even infinite sequences without needing to store every value in a list.
Move faster with Replit
Replit is an AI-powered development platform that transforms natural language into working applications. Describe what you want to build, and Replit Agent creates it—complete with databases, APIs, and deployment.
Replit Agent can take the concepts from this article and turn them into production-ready applications. For example, you could use it to:
- Build a text analysis dashboard that returns word count, character count, and a readability score, expanding on the logic from the
analyze_textfunction. - Create a prime factorization utility that takes a number and returns a list of its prime factors, just like the
get_prime_factorsexample. - Deploy a simple API that returns paginated search results as a structured object, bundling data with metadata like the current page and total count.
Describe your app idea, and the agent will write the code, test it, and fix issues automatically. Try Replit Agent and turn your concepts into working applications faster.
Common errors and challenges
While powerful, returning multiple values introduces unique challenges that can lead to subtle bugs if you're not prepared for them.
- Forgetting to unpack the correct number of values: A
ValueErroris a common issue that occurs when you try to unpack a different number of variables than the function returns. Always ensure the number of variables on the left of the assignment matches the number of items in the returned tuple to avoid this crash. - Accidentally modifying returned mutable objects: It's easy to forget that changing a returned list or dictionary can create side effects. Because the function returns a reference, not a copy, any modifications you make will affect the object everywhere it's used, leading to hard-to-trace bugs.
- Confusion with
Nonein multiple return values: A function might return a value orNoneas part of a tuple, which can cause aTypeErrorif your code isn't prepared. It’s good practice to check forNonebefore you try to use a returned value that might be empty.
Forgetting to unpack the correct number of values
A classic mistake is trying to unpack more or fewer values than a function returns. Python expects a perfect one-to-one match, and your program will crash with an error if the counts don't align.
The code below shows this in action. The get_user_details function sends back three values, but we only try to assign them to two variables. See what happens.
def get_user_details():
return "Alice", 30, "Developer"
name, age = get_user_details()
print(f"Name: {name}, Age: {age}")
Here, get_user_details() sends back three values, but the code tries to fit them into just two variables, name and age. This mismatch is what causes the crash. See how to fix this in the corrected example below.
def get_user_details():
return "Alice", 30, "Developer"
name, age, role = get_user_details()
print(f"Name: {name}, Age: {age}, Role: {role}")
The fix is straightforward—you just need to match the number of variables to the number of returned values. By adding the role variable, the assignment now correctly unpacks all three values from get_user_details().
- This simple change prevents the
ValueErrorand lets your program run as expected.
It’s a good habit to double-check your assignments, especially when refactoring functions that return multiple items, to ensure the counts align perfectly.
Accidentally modifying returned mutable objects
Modifying a returned list or dict can lead to tricky bugs because you're altering the original object, not a copy. This means the change unexpectedly appears elsewhere in your code. The example below shows how this happens with a settings dictionary.
def get_default_settings():
return {"theme": "dark", "font_size": 12, "notifications": True}
settings = get_default_settings()
settings["theme"] = "light"
user_settings = get_default_settings()
print(user_settings) # Expected original settings
The second call to get_default_settings() returns the modified dictionary, not the original defaults you'd expect. This happens because the change to settings alters the underlying object. See how to prevent this side effect in the corrected code.
def get_default_settings():
return {"theme": "dark", "font_size": 12, "notifications": True}.copy()
settings = get_default_settings()
settings["theme"] = "light"
user_settings = get_default_settings()
print(user_settings) # Still has original settings
The solution is to return a shallow copy of the dictionary by calling .copy(). This simple change ensures that modifications to the returned settings object don't alter the original defaults inside the get_default_settings function. Now, every call provides a fresh, clean copy, preventing unexpected side effects.
- This is crucial when returning mutable objects like lists or dictionaries, as it isolates changes and keeps your function's behavior predictable.
Confusion with None in multiple return values
A function designed to return multiple values might sometimes return None instead, especially when a condition isn't met. This is a classic setup for a TypeError because your code can't unpack a value that doesn't exist. The following example shows what happens.
def find_user(user_id):
users = {1: "Alice", 2: "Bob"}
if user_id in users:
return users[user_id], True
return None
name, success = find_user(3)
print(f"Found user: {name}")
Here, find_user(3) returns None because the user isn't in the dictionary. The code then fails when it tries to unpack that single None value into the name and success variables. See how to fix this below.
def find_user(user_id):
users = {1: "Alice", 2: "Bob"}
if user_id in users:
return users[user_id], True
return None, False
name, success = find_user(3)
if success:
print(f"Found user: {name}")
else:
print("User not found")
The fix is to ensure your function always returns the same number of values, even on failure. The corrected find_user function now returns None, False when a user isn't found. This prevents a TypeError because the unpacking statement always receives two values.
- You can then use the boolean flag to check if the operation succeeded before using the other returned values. This is a robust pattern for any function that might fail, like database lookups.
Real-world applications
Beyond the theory and common pitfalls, these return strategies are fundamental to everyday tasks like parsing file paths and extracting user data.
Parsing file paths with the return statement
Returning a tuple is a clean way to deconstruct a file path, letting you extract its directory, filename, and extension in a single function call.
def analyze_file_path(path):
import os
directory = os.path.dirname(path)
filename = os.path.basename(path)
name, ext = os.path.splitext(filename)
is_image = ext.lower() in ['.jpg', '.jpeg', '.png', '.gif']
return directory, name, ext, is_image
filepath = "/home/user/documents/vacation.jpg"
folder, name, extension, is_image = analyze_file_path(filepath)
print(f"Location: {folder}")
print(f"File: {name}{extension}, Image: {is_image}")
The analyze_file_path function uses Python's built-in os module to process a file path string. It efficiently extracts several key pieces of information in a single operation.
- Functions like
os.path.dirnameandos.path.splitextseparate the directory, filename, and extension. - It also determines if the file is an image by checking its extension, returning a simple
TrueorFalsevalue.
All this information is returned together and unpacked into clearly named variables, making the data easy to work with immediately.
Extracting user statistics with return
Functions that return multiple values are also perfect for summarizing datasets, such as calculating several key metrics from a list of user records.
def process_user_stats(user_records):
total_users = len(user_records)
active_users = sum(1 for user in user_records if user['active'])
avg_age = sum(user['age'] for user in user_records) / total_users
premium_percentage = sum(1 for user in user_records if user['premium']) / total_users * 100
return total_users, active_users, avg_age, premium_percentage
users = [
{'id': 1, 'age': 28, 'active': True, 'premium': False},
{'id': 2, 'age': 35, 'active': True, 'premium': True},
{'id': 3, 'age': 42, 'active': False, 'premium': True}
]
count, active, avg_age, premium_pct = process_user_stats(users)
print(f"Users: {count} total, {active} active")
print(f"Average age: {avg_age:.1f}, Premium: {premium_pct:.1f}%")
The process_user_stats function shows a powerful way to process collections. It uses generator expressions with sum() to efficiently count active users and calculate percentages, all within a single pass over the user_records list. This is much faster than looping through the data multiple times.
- It computes four values: total users, active users, average age, and the percentage of premium users.
- All four results are returned at once and unpacked into individual variables like
countandavg_age.
This pattern keeps your calling code clean, as each metric gets its own descriptive variable.
Get started with Replit
Turn these concepts into a real tool. Describe your idea to Replit Agent, like “build a text analyzer that returns word count and sentence count” or “create a temperature converter that returns Fahrenheit and Kelvin.”
The agent writes the code, tests for errors, and deploys your application automatically. Start building with Replit.
Create and deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.
Create & deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.



%2520in%2520Python.png)