Return Multiple Values JavaScript: A Practical Guide

Master return multiple values javascript in JavaScript by returning arrays or objects and applying destructuring, defaults, and robust patterns for clearer APIs.

JavaScripting
JavaScripting Team
·5 min read
Quick AnswerDefinition

In JavaScript, you usually return a single value, but you can expose multiple results by packaging them in an object or array and then destructuring at the call site. This keeps APIs expressive and scalable. The two core patterns are object-based returns and array-based tuples, each with own tradeoffs.

Why returning multiple values matters in JavaScript

Functions in JavaScript traditionally return one value. When a function computes several related results, wrapping them in a single composite value—an object or an array—improves readability and reduces boilerplate in calling code. This approach aligns with practical API design, where consumers expect named data rather than positional indices. In this article, we explore ways to implement and consume multiple-return patterns, with emphasis on clarity and maintainability. The phrase return multiple values javascript often appears in developer discussions about destructuring and expressive APIs.

JavaScript
// Pattern 1: return values as an object function createUser(id, name) { const role = 'user'; return { id, name, role }; } const { id, name, role } = createUser(1, 'Alex'); console.log(id, name, role); // 1 Alex user
JavaScript
// Pattern 2: return values as an array (tuple-like) function getCoordinates() { const x = 12; const y = 7; return [x, y]; } const [x, y] = getCoordinates(); console.log(x, y); // 12 7

Choosing a pattern: Objects give typed, named fields and a stable API surface, while arrays are compact and integrate well with destructuring. Consider how your API will evolve and how callers will access values in the future.

Patterns: destructuring arrays as tuples

Destructuring is the ergonomic enabler for returning multiple values as arrays. It lets you bind each position to a variable in a single, readable statement. When a function returns an array, you can pull elements into distinct names, making the code self-documenting. You can also supply default values to handle missing elements.

JavaScript
function getPoint() { return [3, 4, true]; } const [x, y, visible] = getPoint(); console.log(x, y, visible); // 3 4 true
JavaScript
function maybeGet() { // simulate a partial result (undefined for some slots) return [5]; } const [a, b = 0] = maybeGet(); console.log(a, b); // 5 0

Destructuring arrays is excellent for positional data, but beware of readability if the meaning of each index isn’t obvious. In such cases, prefer an object return for self-documenting access.

Objects for named values and stable API

Returning an object makes the meaning of each value explicit. This is especially helpful when there are many fields or when the order of values might change. Destructuring an object preserves property names and supports renaming, defaults, and nested extraction. This pattern is common for configuration, results, and data transfer objects.

JavaScript
function areaAndPerimeter(w, h) { return { area: w * h, perimeter: 2 * (w + h) }; } const { area, perimeter } = areaAndPerimeter(5, 3); console.log(area, perimeter); // 15 16
JavaScript
// Nested/destructured example function getStats() { return { min: 0, max: 100, avg: { value: 50, label: 'mean' } }; } const { min, max, avg: { value: mean } } = getStats(); console.log(min, max, mean); // 0 100 50

Objects offer named fields, which is often clearer than array indices, and they scale well as you add more data. They also simplify refactoring since callers rely on property names rather than positions.

Default values and renaming with object destructuring

Defaults and aliasing are powerful when functions return incomplete data or when callers require different field names. You can provide defaults directly in the destructuring pattern and rename properties for local convenience. This makes your consuming code robust against partial results while preserving a clean API surface.

JavaScript
function fetchConfig() { return { host: 'example.com' }; } const { host, port = 8080 } = fetchConfig(); console.log(host, port); // example.com 8080
JavaScript
// Renaming and defaults together function getUser() { return { id: 42, username: 'sam' }; } const { id: userId, username: userName = 'guest' } = getUser(); console.log(userId, userName); // 42 sam

Defaults help when data might be missing, while renaming keeps internal naming aligned with your domain terms. This pattern reduces boilerplate and improves readability across your codebase.

Nested values and partial returns

Real-world data often arrives in nested objects. Returning a nested structure via an object is common; you can extract only what you need with nested destructuring. When a function returns data that may not exist, you can provide safe defaults or check for an error field. This promotes resilient code that gracefully handles partial results.

JavaScript
function buildUser() { return { id: 7, profile: { name: 'Jordan', email: '[email protected]' }, roles: ['admin', 'editor'] }; } const { id, profile: { name, email } } = buildUser(); console.log(id, name, email); // 7 Jordan [email protected]
JavaScript
function fetchUser(id) { if (!id) return { error: 'missing_id' }; return { data: { id, name: 'Alex' } }; } const { data, error } = fetchUser(2); if (error) { console.error(error); } else { console.log(data.id, data.name); // 2 Alex }

Nested patterns are powerful but can become verbose. Layer destructuring to keep consumers focused on what they need, not every nested path.

Error handling and the { error, data } pattern

A pragmatic approach to return multiple values is to standardize on a result shape like { error, data }. This lets you pattern-match quickly and reduces scattered error handling logic. When no error exists, data contains the payload; when an error occurs, you can short-circuit or retry. This approach scales well for API wrappers, utilities, and data-processing pipelines.

JavaScript
function loadResource(url) { try { // pretend fetch if (!url) throw new Error('url_required'); const payload = { url, content: 'OK' }; return { data: payload }; } catch (e) { return { error: e.message }; } } const { data, error } = loadResource('/api/resource'); if (error) { console.error('Load failed:', error); } else { console.log(data.url, data.content); // /api/resource OK }
JavaScript
// Consumer-side pattern for consistency const result = loadResource('/api/resource'); if (result.error) { // handle error } else { const { url, content } = result.data; // work with url/content }

Choosing the right shape early helps teammates understand contract expectations and reduces defensive coding. When you introduce heavy payloads, consider streaming or pagination to avoid large early returns.

Practical patterns and performance considerations

Performance considerations matter when you return large data structures. Returning a single object or array involves creating a container and, depending on the data, copying references instead of duplicating values. In practice, prefer minimal, well-scoped return values and avoid deep cloning unless necessary. Profiling with real workloads helps identify whether destructuring imposes meaningful overhead. For most front-end workloads, the clarity gained by explicit return shapes outweighs minor costs. When data grows, consider lazy evaluation, pagination, or streaming to keep responses small and manageable.

JavaScript
function summarize(records) { // returns a compact object with only essential stats const count = records.length; const sum = records.reduce((a, r) => a + r.value, 0); const mean = count ? sum / count : 0; return { count, mean }; } console.log(summarize([{value:1},{value:2},{value:3}]));
JavaScript
// If you need more than one pass, avoid copying by returning references to existing data function analyze(dataset) { // compute and return a view-like object return { min: Math.min(...dataset), max: Math.max(...dataset), range: Math.max(...dataset) - Math.min(...dataset) }; }

In summary, design your return shape with API ergonomics in mind. Favor named fields for clarity, keep dependencies small, and document the contract so future changes don’t force breaking consumer code.

Steps

Estimated time: 15-25 minutes

  1. 1

    Design the return type

    Decide whether to return an object or an array based on API readability and future changes. Document the contract in comments or a spec.

    Tip: Prefer object returns for named fields to reduce reliance on order.
  2. 2

    Implement the function

    Return the chosen structure from your function and keep calls clean and predictable.

    Tip: Keep the return value focused on related data to avoid large, monolithic objects.
  3. 3

    Consume with destructuring

    Extract needed values using object or array destructuring. Consider defaults where data may be missing.

    Tip: Always provide sensible defaults to minimize runtime errors.
  4. 4

    Add error signaling

    Use a consistent { error, data } pattern if your function can fail. Check error before using data.

    Tip: Define a stable error shape to simplify caller code.
  5. 5

    Test and document

    Write focused tests for both successful and error cases. Document the expected shape of returns.

    Tip: Automated tests catch regressions in return contracts.
Pro Tip: Use object returns when you want self-documenting code and easy extension of fields.
Warning: Avoid deep nesting in returns; it increases destructuring complexity and can hurt readability.
Note: Combine defaults with destructuring to handle partial data safely.

Prerequisites

Required

Optional

  • VS Code or any code editor
    Optional

Keyboard Shortcuts

ActionShortcut
CopyCopies selected code or text in editorCtrl+C
PastePastes from clipboard into editorCtrl+V
UndoReverts last changeCtrl+Z
RedoReapplies last undone actionCtrl+Y

Questions & Answers

What is the difference between returning an object vs an array in JavaScript?

Returning an object provides named fields, enhancing readability and stability as the API evolves. Arrays are compact and work well with destructuring for positional data. Choose based on whether value meaning is better expressed by a name (object) or by position (array).

Objects give you named fields for clarity, while arrays are great when order matters and you want a compact return.

How do I destructure with defaults?

Destructure with defaults by providing values after the equals sign in the pattern. This protects against missing properties and keeps code robust when the return shape can vary.

Use defaults in destructuring to handle missing fields gracefully.

Can I return nested values safely?

Yes. You can use nested destructuring to extract deeply nested values. If parts of the structure may be absent, combine optional chaining with defaults to avoid runtime errors.

Yes, you can pull nested values safely with careful destructuring and defaults.

What pattern is best for error handling in returns?

Adopt a consistent result shape like { error, data }. Check error before using data in the caller to simplify control flow and error reporting.

Use a standard { error, data } shape so callers handle issues clearly.

Is there a performance cost to returning multiple values?

The overhead is typically small and dominated by allocations of the return container. Profile important paths; for most apps, destructuring readability wins.

Performance impact is usually minor; focus on clarity and maintainability.

When should I avoid returning multiple values altogether?

If the data is not logically related or the API would become confusing, consider smaller, targeted returns or refactoring into separate functions to keep concerns isolated.

If data isn’t clearly related, split into separate concerns instead.

What to Remember

  • Return values via objects or arrays based on API clarity
  • Destructure at the call site for clean code
  • Prefer named fields to positional indices when readability matters
  • Use defaults and error shapes to make APIs robust
  • Test return contracts to prevent regressions

Related Articles