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.
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.
// 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// 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 7Choosing 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.
function getPoint() {
return [3, 4, true];
}
const [x, y, visible] = getPoint();
console.log(x, y, visible); // 3 4 truefunction maybeGet() {
// simulate a partial result (undefined for some slots)
return [5];
}
const [a, b = 0] = maybeGet();
console.log(a, b); // 5 0Destructuring 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.
function areaAndPerimeter(w, h) {
return { area: w * h, perimeter: 2 * (w + h) };
}
const { area, perimeter } = areaAndPerimeter(5, 3);
console.log(area, perimeter); // 15 16// 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 50Objects 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.
function fetchConfig() {
return { host: 'example.com' };
}
const { host, port = 8080 } = fetchConfig();
console.log(host, port); // example.com 8080// Renaming and defaults together
function getUser() {
return { id: 42, username: 'sam' };
}
const { id: userId, username: userName = 'guest' } = getUser();
console.log(userId, userName); // 42 samDefaults 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.
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]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.
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
}// 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.
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}]));// 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
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
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
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
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
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.
Prerequisites
Required
- Required
- Understanding of ES6+ features (destructuring, spread/rest, object literals)Required
- Basic knowledge of functions and return valuesRequired
Optional
- VS Code or any code editorOptional
Keyboard Shortcuts
| Action | Shortcut |
|---|---|
| CopyCopies selected code or text in editor | Ctrl+C |
| PastePastes from clipboard into editor | Ctrl+V |
| UndoReverts last change | Ctrl+Z |
| RedoReapplies last undone action | Ctrl+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
