5 Ways to Return Multiple Values in TypeScript

TypeScript functions can technically return only one value, but in real-world projects, we often need to return several pieces of data at once, such as the result of an operation, an error message, and some metadata.

There are multiple ways to model this in TypeScript, and each option comes with different trade-offs for readability, type safety, and maintainability. Choosing the wrong approach can make your code harder to understand and refactor later.

In this tutorial, you’ll learn five practical patterns to return multiple values from a function in TypeScript: arrays, tuples, objects with interfaces, discriminated unions, and classes. For each pattern, you’ll see when to use it, when to avoid it, and how it’s used in real-world scenarios.

Why Return Multiple Values in TypeScript?

In many applications, returning a single primitive or object is not enough. Consider these common scenarios:

  • A login function needs to return whether authentication succeeded, the user’s token, and the token expiry time.
  • A data fetch function might want to return both data and information about the HTTP status and any error.
  • A validation function may need to return the cleaned value and a list of validation messages.

TypeScript provides several patterns to model these cases without compromising type safety. Your job as a developer is to pick the pattern that best fits your use case.

Methods to Return Multiple Values in TypeScript

In this section, we’ll look at five methods:

  • Using arrays
  • Using tuples
  • Using objects and interfaces
  • Using discriminated unions for success/error
  • Using custom classes

At the end, you’ll find a decision guide that helps you choose the right method for your scenario.

Use Arrays: Best for Ordered, Same-Type Returns

Arrays are the simplest way to return multiple values when the order of values matters, and the values are closely related.

function getMinMax(numbers: number[]): [number, number] {
const min = Math.min(...numbers);
const max = Math.max(...numbers);
return [min, max];
}

const [min, max] = getMinMax([3, 1, 8, 2, 5]);
console.log(`Min: ${min}, Max: ${max}`); // Min: 1, Max: 8

You can see the output in the screenshot below.

5 Ways to Return Multiple Values in TypeScript

Here, we return the minimum and maximum values as a pair. We then use array destructuring to access them in a concise way.

When to use arrays

  • When values are strongly ordered (like min/max, x/y coordinates).
  • When values have the same logical meaning or type (e.g., a list of numbers or strings).

When to avoid arrays

If the values have different semantic meanings (for example, nameageisActive), an array like [name, age, isActive] is harder to understand than an object, because result[0] or result[1] doesn’t explain what the value represents.

Use Tuples: The TypeScript-Native Pattern

Tuples are a TypeScript feature that extends arrays with fixed length and specific types for each position. This makes them ideal for returning multiple values of different types in a fixed order.

Example: Return Auth Result as a Tuple

type AuthResult = [success: boolean, token: string, expiresIn: number];

function authenticate(username: string, password: string): AuthResult {
if (username === "admin" && password === "secret") {
return [true, "abc123token", 3600];
}

return [false, "", 0];
}

const [isAuthenticated, token, expiresIn] = authenticate("admin", "secret");

if (isAuthenticated) {
console.log(`Token: ${token}, Expires in: ${expiresIn}s`);
}

You can see the output in the screenshot below.

Ways to Return Multiple Values in TypeScript

In this example:

  • We define a labeled tuple type AuthResult.
  • Each element has a specific meaning and type.
  • Labeled tuples improve editor hints and readability.

Real-world connection

React’s useState hook is a classic example of this pattern:

const [count, setCount] = useState(0);

It returns a tuple: the current state and a setter function. This is one of the most common patterns for returning multiple values in TypeScript-heavy codebases.

When to use tuples

  • When the number of values is fixed.
  • When you have mixed types in a specific order.
  • When you’re designing hook-like APIs or functional-style utilities.

Tip: controlling inference

Sometimes TypeScript will infer an array type like (string | number)[] instead of a tuple. You can fix this by:

  • Declaring an explicit tuple type (as shown above), or
  • Using as const when returning a literal:
function getPair() {
return ["left", "right"] as const; // inferred as readonly ["left", "right"]
}

Use Objects and Interfaces: The Most Readable Approach

Objects are the most self-descriptive way to return multiple values. Each property has a name that tells you what it represents. Combining objects with interfaces or type aliases gives you strong typing and readability.

Example: Return User Data as an Object

interface UserData {
name: string;
age: number;
isActive: boolean;
}

function getUserData(): UserData {
return {
name: "Jane Smith",
age: 25,
isActive: false,
};
}

const userData = getUserData();
console.log(`Name: ${userData.name}, Age: ${userData.age}, Active: ${userData.isActive}`);

This approach clearly describes what each value means and allows TypeScript to catch missing or incorrect properties.

interface vs type

You could also write:

type UserData = {
name: string;
age: number;
isActive: boolean;
};

Use interface when:

  • You expect to extend the type in other files.
  • You want to use declaration merging.

Use type when:

  • It’s a one-off shape.
  • You’re combining unions or intersection types.

When to use objects

  • When you want readable, self-documenting code.
  • When the set of returned values might grow over time.
  • When you need to pass the result through multiple layers, and readability matters.

For many teams, returning objects with interfaces is the default recommendation.

Use Discriminated Unions: Safe Success and Error Returns

A very common pattern is a function that can either succeed (returning a value) or fail (returning an error). Instead of throwing errors or returning null, you can use a discriminated union to model both cases in a type-safe way.

Example: Result Type for JSON Parsing

type Success<T> = {
ok: true;
value: T;
};

type Failure = {
ok: false;
error: string;
};

type Result<T> = Success<T> | Failure;

function parseJSON(input: string): Result<object> {
try {
const parsed = JSON.parse(input);
return { ok: true, value: parsed };
} catch {
return { ok: false, error: "Invalid JSON string" };
}
}

const result = parseJSON('{"name": "Alice"}');

if (result.ok) {
console.log("Parsed value:", result.value);
} else {
console.error("Error:", result.error);
}

You can see the output in the screenshot below.

5 Ways to Return Multiple Values TypeScript

Here:

  • Result<T> can be either a success or failure.
  • The ok property acts as the discriminant.
  • TypeScript automatically narrows the type inside each branch of the if statement.

Why this is powerful

  • You force the caller to handle both success and failure.
  • You avoid returning loosely typed values like any or null.
  • The types stay in sync as your function evolves.

When to use discriminated unions

  • For functions that can fail in expected ways, such as parsing, validation, or network calls.
  • When you want to keep your code functional and predictable, without throwing exceptions in normal control flow.

Use Custom Classes: When Your Return Value Needs Behavior

Sometimes you need to return not just data, but also related behavior (methods). In these scenarios, a class can encapsulate both.

Example: User Data with Methods

class UserData {
constructor(
public name: string,
public age: number,
public isActive: boolean
) {}

celebrateBirthday(): void {
this.age += 1;
}

deactivate(): void {
this.isActive = false;
}
}

function getUserData(): UserData {
return new UserData("Michael Brown", 40, true);
}

const userData = getUserData();
userData.celebrateBirthday();
console.log(`Name: ${userData.name}, Age: ${userData.age}, Active: ${userData.isActive}`);

In this example:

  • UserData encapsulates both data (properties) and operations (methods).
  • The function returns a fully initialized instance ready to be used.

When to use classes

  • When you want to bundle data and behavior together.
  • When following an object-oriented design.
  • When you need lifecycle methods or want to enforce invariants via constructors.

When not to use classes

  • For simple data structures with no behavior.
  • When you just need to transport data between layers. In those cases, interfaces and objects are simpler.

Which Method Should You Use? (Quick Decision Guide)

Use this guide when you’re not sure which pattern is appropriate:

MethodBest ForTypeScript Benefit
ArrayOrdered values with similar meaningSimple and concise
TupleFixed count, mixed types, hook-like APIsStrong positional typing, editor support
Object + Interface/TypeReadable, named values, evolving shapesSelf-documenting, compiler-checked properties
Discriminated UnionSuccess/error results, parse/validate patternsSafe type narrowing, avoids unchecked any
ClassData plus behavior and methodsEncapsulation, OOP-friendly

Practical rule of thumb

  • If you’re unsure, start with an object + interface.
  • Use tuples when you want a compact, hook-style API.
  • Use discriminated unions whenever you need to return either a value or an error in a predictable way.

Frequently Asked Questions

Can a TypeScript function return two different types?

Yes. You can model this using a union type or a discriminated union. For example:
type NumberOrString = number | string; function parseNumber(input: string): NumberOrString { const parsed = Number(input); return isNaN(parsed) ? input : parsed; }
However, discriminated unions are usually safer because they enforce explicit handling of each case.

What is the difference between a tuple and an array in TypeScript?

An array like number[] is a list of values of the same type with variable length.
tuple like [string, number] has a fixed length and specific types at each position.
Use tuples when the position has meaning and you know exactly how many elements there are.

How do I return a value and an error message together?

Use either an object or a discriminated union:
type ValidationResult = {
value: string | null;
error: string | null;
};

function validateEmail(email: string): ValidationResult {
if (!email.includes(“@”)) {
return { value: null, error: “Invalid email address” };
}

return { value: email, error: null };
}
Or use the Result<T> pattern shown earlier for more robust handling.

Can async functions return multiple values in TypeScript?

Yes. The same patterns apply; you just wrap the return type in a Promise:
interface UserWithToken {
userId: string;
token: string;
}

async function loginAsync(): Promise<UserWithToken> {
// simulate async call
return {
userId: “123”,
token: “abc123”,
};
}
The calling code can then await the function and destructure or access the properties as needed.

Conclusion

Returning multiple values in TypeScript is less about syntax and more about choosing the right pattern for your situation. Arrays and tuples are concise and great for ordered values, objects with interfaces are the most readable and flexible, discriminated unions give you safe success/error handling, and classes shine when your return values need behavior as well as data.

By understanding these five patterns and when to use each, you can write TypeScript code that is both expressive and maintainable, and your functions will communicate their intent clearly to anyone reading your code.

You may also read:

51 Python Programs

51 PYTHON PROGRAMS PDF FREE

Download a FREE PDF (112 Pages) Containing 51 Useful Python Programs.

pyython developer roadmap

Aspiring to be a Python developer?

Download a FREE PDF on how to become a Python developer.

Let’s be friends

Be the first to know about sales and special discounts.