JavaScript Return Multiple Values: Best Tips & Tricks

Sohaib IlyasSohaib Ilyas
13 min read

Why JavaScript's Single Return Limitation Sparked Better Code

It might seem a bit backward, but JavaScript's rule of only letting a function return one thing is actually a feature, not a bug. This core design choice didn't hold developers back; instead, it pushed us to write more organized and thoughtful code. Think of it as a creative constraint that paved the way for the clean patterns we rely on today. It makes you stop and think about what a function's output really is, which naturally leads to better data structures.

This principle is the reason we need methods to javascript return multiple values in the first place. This single-return rule keeps a function’s job focused and clear, which cuts down on confusion. If you're interested in diving deeper into these foundational ideas, there are many great articles on JavaScript that cover them.

Common Scenarios Demanding Multiple Values

In your everyday coding, you'll constantly run into situations where just one piece of data from a function isn't going to cut it. This is where bundling values together becomes necessary for building apps that work correctly and don't break easily.

I've seen these cases pop up all the time:

  • API Responses: When you ask an API for data, you almost always need two things back: the data payload itself and a status code (like 200 OK or 404 Not Found). Getting both is crucial for gracefully handling success or failure in your app.

  • Form Validation: Imagine a function that checks a user's signup form. It needs to give you back both the cleaned-up, safe user input and a list of any validation errors. This lets you show specific error messages to the user while still having the valid data ready to go.

  • Complex Calculations: A function that figures out a mortgage payment might need to return several pieces of information: the monthly payment, the total interest you'll pay over the loan's life, and the full amortization schedule.

Recognizing these real-world needs is the first step. It helps you see that the techniques we’re about to cover—like using arrays and objects—aren’t just clunky workarounds. They are powerful tools for writing code that is easy to read and maintain.

Arrays: When Position Tells The Whole Story

An abstract image representing data being organized into an array structure

When the order of your data is just as important as the data itself, arrays are your best friend. I often reach for them in scenarios where the position has a natural meaning, like a function returning [x, y] coordinates, [red, green, blue] color values, or a user’s [firstName, lastName]. In these cases, the array’s structure is self-explanatory, making it a clean way to javascript return multiple values.

Interestingly, JavaScript was designed so that a function can only return one item. This limitation forced developers to get creative, leading to the common practice of using containers like arrays. As the language evolved, this method stuck around because of its simplicity, especially when combined with modern destructuring. If you're curious about how the community adapted, CoreUI's blog has a good overview.

Unpacking Arrays with Destructuring

Before ES6 came along, we were stuck accessing values with index numbers like result[0] and result[1], which wasn't very descriptive and could make code hard to read. Thankfully, modern destructuring syntax completely changed the game.

Let’s say you have a function that fetches a user's location and their connection status:

function getUserState() { // Logic to get user's location and status const location = "New York, NY"; const status = "active"; return [location, status]; }

const [userLocation, userStatus] = getUserState();

console.log(userLocation); // "New York, NY" console.log(userStatus); // "active"

Look how clean and readable that is. The variables userLocation and userStatus are immediately assigned based on their position in the array. This positional approach is highly efficient when you’re dealing with a small, fixed set of related values.

Best Practices and Common Pitfalls

While arrays are powerful, they come with one big catch: order is critical. If you change the return order in the function, you have to hunt down and update every single place where you destructure it. Forgetting to do so can introduce silent, hard-to-find bugs.

To help you use arrays effectively, here are some tips I've picked up:

  • Keep it Small: Arrays work best for returning 2 to 3 related values. Any more than that, and it becomes a memory game trying to remember the correct order.

  • Use Consistent Ordering: Always return values in the same sequence. This simple discipline helps prevent frustrating logic errors down the line.

  • Leverage Default Values: You can prevent errors from undefined values by setting defaults right in the destructuring assignment. It’s a great defensive coding habit: const [data, error = 'No error'] = fetchData();

By keeping these simple rules in mind, you can make your functions that return arrays both robust and easy for you (and your team) to maintain.

Object Returns: Writing Code That Explains Itself

While arrays are great for ordered data, they have a major drawback: they can be cryptic. If your function returns [data, 200, true], what does that true value actually mean? A week from now, you might be scratching your head trying to remember. This is where returning an object really shines. It turns your function's output into self-documenting code, making it instantly understandable.

Using objects to javascript return multiple values isn't just some clever trick; it's a widely accepted best practice in the development community. In fact, surveys of JavaScript developers show that over 70% regularly use object destructuring to manage multiple return values from functions. It's a standard approach that you'll see recommended by top programming resources like the MDN Web Docs, which speaks volumes about its place in modern coding.

From Confusing to Clear: An E-commerce Example

Let's imagine you're working on an e-commerce application and need a function to validate a promo code. This function needs to return a few things: the discount percentage, the minimum purchase amount, and an error message if the code is invalid.

If you were to use an array, a failed validation might return something like this: return [null, null, "Invalid promo code"];

This is pretty ambiguous. You have to remember which index corresponds to which piece of information. Now, let’s see how much clearer it becomes with an object:

function validatePromoCode(code) { // ...some validation logic here if (!isValid) { return { discount: 0, minPurchase: 0, error: "Invalid promo code" }; }

return { discount: 15, minPurchase: 50, error: null }; }

const { discount, error } = validatePromoCode("SAVE15");

See the difference? The keys (discount, minPurchase, error) act as clear labels for each value. There's zero guesswork involved. When you use destructuring to unpack the object, you can pull out only the properties you need, and the order is irrelevant. This simple change makes your code more robust and much easier to maintain and debug in the long run.

Destructuring: The Syntax That Changed Everything

An abstract image representing the clean, organized nature of destructuring assignment.

When ES6 introduced destructuring assignment, it felt like a game-changer. Suddenly, using arrays and objects to return multiple values in JavaScript went from a clunky workaround to a genuinely elegant pattern. It’s so much more than a simple shortcut; it changes how we interact with data, making our code cleaner and easier to read. Instead of digging for values with data[0] or response.user, you can pull them out directly into clearly named variables.

Think about the last time you refactored an older function. Before destructuring, you likely saw a bunch of temporary variables and repetitive property access. After applying destructuring, all that noise disappears, often replaced by a single, self-explanatory line. This shift is a huge win for code readability and maintainability.

Advanced Destructuring Patterns

Once you get the basics down, destructuring has a few more tricks up its sleeve that I find myself using all the time to write more stable code. These features help handle tricky data scenarios gracefully and prevent common bugs.

  • Setting Default Values: One of the quickest ways to get a runtime error is trying to read a property from an undefined value. Destructuring lets you provide a fallback value right where you declare the variable, which is a fantastic defensive move. const { user, error = 'No error occurred' } = handleRequest();

  • Renaming Variables: Have you ever had a returned object key clash with a variable you're already using? With destructuring, you can rename it instantly, avoiding any naming conflicts without needing extra lines of code. const { id: userID } = fetchUserData(); // 'id' from the object is now 'userID'

  • Handling Nested Data: API responses are rarely simple, flat objects. Nested destructuring is your best friend here. It lets you reach deep into an object or array to grab the exact data you need, skipping all the intermediate steps. const { data: { user: { name } } } = apiResponse; Getting comfortable with these patterns will really change how you approach function outputs. It makes unpacking data less of a chore and more of a clean, safe, and readable process.

TypeScript: Making Multiple Returns Bulletproof

While JavaScript gives you some flexible ways to return multiple values, TypeScript swoops in to add a layer of safety that can squash bugs before they even get a chance to hatch. If you've ever destructured an array and accidentally mixed up the order, or forgotten a key from an object you were expecting back from a function, you know just how frustrating those little slip-ups can be. TypeScript helps put an end to this by letting you define exactly what your function's output should look like, turning would-be runtime errors into helpful compile-time checks.

Defining Your Returns with Tuples and Interfaces

When you're returning an array and the order of the items is super important, TypeScript has a neat feature called a tuple. Think of a tuple as an array with a fixed number of elements where the type of each element is known ahead of time.

For instance, a function that returns user coordinates becomes totally foolproof: function getCoordinates(): [number, number] { return [10, 20]; } If you tried to return [10, "20"] or just [10], TypeScript would immediately throw an error, saving you from a potential headache down the line.

When it comes to object returns, interfaces offer a clear and reusable contract for what your data should look like. You can define the "shape" of the returned object once and then use that definition everywhere.

interface UserProfile { id: number; email: string; isActive: boolean; }

function fetchUser(): UserProfile { // ... fetching logic return { id: 1, email: 'user@example.com', isActive: true }; } This approach is naturally self-documenting, making your code much easier for you and your team to read and maintain. This is particularly helpful in larger applications, a common point of discussion in full-stack development.

Why Typed Returns Matter

The fact that JavaScript functions can only return a single value is what led to the development of these stricter patterns in typed supersets like TypeScript. This drive for type safety is a huge reason for its popularity. In fact, some analyses show that over 66% of professional developers use TypeScript, partly for the guarantees it provides around function returns. You can read more about these insights into function return patterns on mindsers.blog.

In the end, using TypeScript to handle how you javascript return multiple values changes a flexible language feature into a rock-solid, predictable development tool.

Advanced Patterns That Separate Pros From Beginners

An abstract image representing complex, advanced coding patterns

Once you've got a good handle on using arrays and objects, you're ready to level up. Let's look at the techniques that experienced developers turn to when simple data structures don't quite solve the problem. These advanced methods for how javascript return multiple values are perfect for more complex situations, especially when dealing with stateful operations or long sequences of data.

Using Generators for Sequential Data

Have you ever found yourself needing a function that hands you values one by one, but only when you ask for them? This is exactly what generator functions were made for. Instead of returning a whole array at once, a generator uses the yield keyword to provide a value each time you call its next() method. This approach is a game-changer for memory efficiency when you're working with huge datasets or even infinite sequences, because you only process one item at a time.

Imagine you need to process a massive log file line by line or generate an endless series of unique IDs. A generator can do this without ever creating a giant, memory-hogging array. It's an incredibly useful tool for controlling how and when you retrieve data, step-by-step.

Class-Based Approaches

What if your function needs to return multiple values that are also tied to specific actions or behaviors? In these cases, using a class is often the most organized and scalable solution. By grouping both the data (as properties) and the logic (as methods) into a single, self-contained object, you create a clear and reusable piece of code that's much easier to test and maintain.

For example, think about a function that processes user information. Instead of just returning a plain object, it could return a new instance of a UserProfile class. This UserProfile object could hold properties like name and email, but also come with built-in methods like getFullName() or isActive(). For a deeper dive into this concept, you might want to review some development best practices. This pattern keeps related data and logic neatly bundled together, preventing your codebase from becoming a tangled mess as it grows.

To help you decide which advanced pattern might be right for your project, here’s a table that breaks down their key characteristics.

Advanced Multiple Return Patterns Overview

A comprehensive overview of advanced techniques including generators, classes, and hybrid approaches with their specific use cases and implementation complexity

PatternComplexity LevelUse CasesBrowser SupportFramework Integration
GeneratorsMediumLarge datasets, infinite sequences, stream processing, async flows (async/await)Excellent (ES6+)Native to JavaScript, often used in libraries like Redux Saga for managing side effects.
ClassesMediumComplex data models, state management, encapsulating data with behaviorExcellent (ES6+)Fundamental to frameworks like Angular and commonly used in React for stateful components.
Async IteratorsHighAsynchronously streaming data from sources like APIs or databasesGood (ES2018+)Increasingly supported; key for modern asynchronous data handling patterns.
Promises + DestructuringLow-MediumHandling multiple asynchronous operations that run in parallel (e.g., Promise.all)Excellent (ES6+)A core pattern in all modern front-end and back-end JS development.

This table shows that while both generators and classes are well-supported and powerful, they solve different kinds of problems. Generators excel at managing sequences of data over time, while classes are unmatched for creating structured, stateful objects.

Your Decision Framework: Choosing The Right Approach

With arrays, objects, and even destructuring at your disposal, picking the best way to return multiple values in JavaScript isn't always straightforward. The "right" choice often has more to do with the context of your project—things like team experience, deadlines, and how easy the code will be to maintain later. It's a balancing act between simplicity, clarity, performance, and readability.

This simple decision tree can help you think through the process, especially when choosing between the two most common methods.

Infographic about javascript return multiple values

The graphic points to a solid rule of thumb I often follow: when you need clear, named keys for your data, especially with 3 or more values, objects are almost always the better choice.

When Simplicity Meets Readability

The decision usually boils down to a trade-off. For a small number of values where the order makes sense (like returning X and Y coordinates), an array is perfectly simple and efficient. But the moment that order becomes unclear, or you start adding more values, the self-documenting power of object keys is a lifesaver. This is where destructuring assignment becomes so useful, letting you pull out values by name and making your code immediately understandable.

This screenshot from the Mozilla Developer Network (MDN) shows the basic syntax for both array and object destructuring.

Screenshot from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment

This syntax is the key to making returns clean, but which one you use depends on your specific situation. My advice is to stick with arrays for positional, concise data. As soon as you need explicit, self-describing keys to keep your code maintainable for the long haul, make the switch to objects.

For more hands-on tutorials covering practical web development challenges, from API integrations to setting up efficient development environments, check out the resources at Sohaib Ilyas.

0
Subscribe to my newsletter

Read articles from Sohaib Ilyas directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Sohaib Ilyas
Sohaib Ilyas