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.

How to return multiple values in Python
Published on: 
Fri
Feb 6, 2026
Updated on: 
Tue
Feb 24, 2026
The Replit Team Logo Image
The Replit Team

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 fibonacci function 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_text function.
  • Create a prime factorization utility that takes a number and returns a list of its prime factors, just like the get_prime_factors example.
  • 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 ValueError is 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 None in multiple return values: A function might return a value or None as part of a tuple, which can cause a TypeError if your code isn't prepared. It’s good practice to check for None before 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 ValueError and 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.dirname and os.path.splitext separate the directory, filename, and extension.
  • It also determines if the file is an image by checking its extension, returning a simple True or False value.

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 count and avg_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.

Get started free

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.

Get started for free

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.