How to return multiple values in Python

Learn how to return multiple values in Python. Explore different methods, tips, real-world applications, and common error debugging.

How to return multiple values in Python
Published on: 
Fri
Feb 6, 2026
Updated on: 
Mon
Apr 13, 2026
The Replit Team

Python functions can return multiple values, a powerful feature to streamline your code. This capability lets you pass complex data between different parts of your program with clean, readable syntax.

In this article, we'll explore several techniques to return multiple values. You'll find practical tips, see real-world applications, and get advice to debug common issues so you can master this essential Python skill.

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'>

When the get_user_info function executes return name, age, is_admin, Python automatically packs these comma-separated values into a single tuple. This is why the output shows ('Alice', 30, True). It’s not three separate return values, but one tuple containing all three items.

This implicit tuple creation is a clean and efficient way to group related data. The result variable holds this tuple, giving you an ordered, immutable collection of your function's output without needing to manually construct a more complex data structure.

Common techniques for returning multiple values

Beyond simply receiving a tuple, you can unpack its values into separate variables or use a list or dict for different 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

Tuple unpacking lets you assign the returned values directly to individual variables. The line width, height = get_dimensions() is a great example of this—it takes the two values returned by the function and assigns them to width and height respectively.

  • This approach is cleaner and more descriptive than accessing elements by index, like result[0].
  • It requires the number of variables on the left to exactly match the number of values in the returned tuple.

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 a perfect choice. The get_prime_factors function demonstrates this by returning a list of prime factors whose length depends on the input number.

  • This gives you flexibility, as the function isn't locked into a fixed number of return values.
  • Lists are also mutable, so you can modify the returned collection if needed—unlike the immutable tuples we saw earlier.

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 dict is an excellent strategy when you want to give your return values clear, descriptive names. In the analyze_text function, keys like "length" and "words" act as labels, making it immediately obvious what each value represents. This approach makes your code more readable and self-documenting.

  • Accessing data by a key, like result["words"], is far more explicit than using an index like result[1].
  • It offers flexibility since you don't need to unpack all the values—you can just retrieve the specific ones you need by name.

Advanced return value strategies

While common techniques cover most cases, you can gain more control and readability with specialized tools like namedtuple, a custom class, and yield.

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 great middle ground between a simple tuple and a full class. It lets you create a custom tuple type where values can be accessed by name, making your code more readable and self-documenting.

  • In the get_stats function, a Stats type is defined with named fields.
  • This allows you to access returned values using clear dot notation, like stats.mean, instead of relying on ambiguous index positions like stats[0].

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 truly complex return values, a custom class is your best option. It allows you to bundle not just data but also related behavior. In the example, the QueryResult class holds the search data and also includes a has_next_page() method to perform logic on that data. This makes the returned object a self-contained unit.

  • This approach packages related attributes and methods into a single, organized object.
  • It’s ideal for returning complex state, such as API responses or database results, because it makes your code more structured and easier to maintain.

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 transforms a function into a generator, which produces values one at a time instead of all at once. In the fibonacci function, yield a pauses execution and sends back the current value of a. When the for loop requests the next item, the function resumes right where it left off, preserving its local state.

  • This approach is highly memory-efficient because it doesn't need to store the entire sequence in memory.
  • It’s perfect for generating large or even infinite sequences, as values are created only when you need them.

Move faster with Replit

Replit is an AI-powered development platform that lets you skip setup and start coding Python instantly. All the necessary dependencies are pre-installed, so you don't have to worry about environment configuration.

While knowing how to return multiple values is useful, you can build complete applications much faster. With Agent 4, you can go from an idea to a working product by simply describing what you want to build. It handles writing the code, connecting to databases, and even deployment.

Instead of piecing together techniques, you can describe the app you want and let Agent 4 build it. For example:

  • A text analysis tool that processes an article and returns a summary of its word count, character length, and other key metrics, just like the analyze_text function.
  • A statistical calculator that takes a list of numbers and generates a report with the mean, median, and mode, similar to the get_stats example.
  • A user profile generator that returns a person's name, age, and permissions status to populate a dashboard, mirroring the get_user_info function.

Simply describe your app, and Replit will write the code, test it, and fix issues automatically, all within your browser.

Common errors and challenges

Returning multiple values is a powerful feature, but you might run into a few common pitfalls along the way.

One of the first hurdles you might encounter is a ValueError. This error pops up when you try to unpack a returned collection into the wrong number of variables—for instance, if a function returns two values but you try to assign them to three. Python raises the error because it doesn't know what to do with the extra variable.

Fixing this is usually straightforward. You can:

  • Ensure the number of variables on the left of the assignment exactly matches the number of items returned.
  • Use an underscore (_) as a placeholder to catch and discard any values you don't need.
  • Assign the entire returned object to a single variable and access elements by index, like result[0], though this often makes your code less readable.

Forgetting to unpack the correct number of values

A common mistake is trying to unpack a returned tuple into the wrong number of variables. Python expects a perfect match—if a function returns three values, you must provide three variables to receive them, or you'll trigger a ValueError.

The code below shows this in action. Notice how the get_user_details() function returns three values, but we only try to assign them to two variables.

def get_user_details():
return "Alice", 30, "Developer"

name, age = get_user_details()
print(f"Name: {name}, Age: {age}")

The unpacking operation fails because there aren't enough variables to hold all the returned values. Python can't assign three items to just two spots, so it raises an error. See how to correct the assignment below.

def get_user_details():
return "Alice", 30, "Developer"

name, age, role = get_user_details()
print(f"Name: {name}, Age: {age}, Role: {role}")

The solution is to match your variables to the returned values. By changing the assignment to name, age, role = get_user_details(), you provide a variable for each of the three items returned by the function. This prevents the ValueError. It's a good habit to be mindful of this whenever you unpack results, as a mismatch between the number of variables and returned values is a common source of errors during development.

Accidentally modifying returned mutable objects

When a function returns a mutable object like a dict, you get a reference to the original, not a copy. Modifying this object can cause unexpected side effects, as changes affect every variable pointing to it. The following code demonstrates this issue.

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

Since both variables point to the same dictionary, modifying settings also changes user_settings. This happens because the function returns a reference, not a unique copy. The code below shows how to prevent this side effect.

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

To fix this, you can return a shallow copy of the dictionary. By adding the .copy() method inside the get_default_settings() function, you ensure each call produces a brand new, independent dictionary. This prevents modifications to one returned object from affecting another.

  • This is a crucial safeguard against unintended side effects.
  • Be mindful of this whenever a function returns a mutable object like a list or dict.

Confusion with None in multiple return values

It's easy to run into a TypeError when a function returns None instead of the expected tuple. This often happens when a search or lookup fails, and the code tries to unpack a non-existent result. See what happens in this example.

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}")

The find_user function returns None because the user isn't found. The code then tries to assign this single, non-iterable value to two separate variables, which isn't possible. See how to handle this scenario correctly 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 solution is to ensure your function always returns a tuple of the expected size, even on failure. Instead of returning a single None value, the corrected find_user function returns None, False. This consistency prevents a TypeError because the unpacking operation always receives two values.

  • You can then check the boolean flag to handle both successful and failed lookups gracefully.
  • This is a crucial pattern for any function that might fail to find a result.

Real-world applications

With an understanding of the potential pitfalls, you can confidently apply these techniques to practical, real-world scenarios.

Parsing file paths with the return statement

Returning multiple values is especially useful for tasks like parsing file paths, where you need to extract several distinct pieces of information from a single string.

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 efficiently deconstructs a file path string using Python's built-in os module. It neatly separates the path into its constituent parts and returns them all in a single operation.

  • The function returns a tuple containing the directory, filename, and extension.
  • It also includes a boolean value that confirms whether the file is an image.

This allows you to unpack all four pieces of information into distinct variables, making the data immediately accessible for further use.

Extracting user statistics with return

Returning multiple values is also perfect for data analysis, letting you compute several key metrics from a dataset in a single function call.

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 how to efficiently summarize a list of dictionaries. It iterates through the user_records to compute four distinct metrics in one pass, bundling the logic neatly.

  • It uses generator expressions inside sum() to conditionally count active and premium users.
  • All calculated values are returned together, allowing you to unpack them into individual variables.

This approach keeps your data processing self-contained and makes the results easy to work with. It’s a practical pattern for any data aggregation task.

Get started with Replit

Turn what you've learned into a real tool. Tell Replit Agent to build "a text analyzer that returns word count and character length" or "a stats calculator that returns mean, median, and mode."

It writes the code, tests for errors, and deploys your app directly from your browser. Start building with Replit.

Build your first app today

Describe what you want to build, and Replit Agent writes the code, handles the infrastructure, and ships it live. Go from idea to real product, all in your browser.

Build your first app today

Describe what you want to build, and Replit Agent writes the code, handles the infrastructure, and ships it live. Go from idea to real product, all in your browser.