Background:
In some tasks, it is required that a function can return different data structures — for example, a fixed-length array (tuple) or multiple variants of a result (union type). In dynamic JavaScript, this approach is widely used for "result or error" patterns (e.g., [err, value]), while in TypeScript it requires clear typing so that subsequent code can properly understand the structure of the result.
Problem:
Without explicitly defining the return type, TypeScript leads the result to too "broad" (i.e., not narrow) a form — an array or union, which deprives the developer of the advantages of autocompletion and type safety. If you destructure the function's result, there is a risk of losing the precise description of the element types.
Solution:
It is necessary to use conditional types and overload function descriptions, and for tuples — explicitly define the resulting array type and use as const (if returning a constant tuple). For union results, you can combine generics and type narrowing to properly distinguish the return type externally.
Code example:
type Result<T> = [null, T] | [Error, null]; function parseNumber(str: string): Result<number> { const n = Number(str); return isNaN(n) ? [new Error('Invalid number'), null] : [null, n]; } const [err, value] = parseNumber('123'); if (err) console.error(err.message); else console.log(value!.toFixed(2)); // With tuple and as const for auto arbitrary structures: function getStatus(flag: boolean): [string, number] | string { return flag ? ['ok', 200] as const : 'error'; } const r = getStatus(true); if (Array.isArray(r)) { // r: readonly [string, number] }
Key features:
Will the type of the array element after destructuring the union returned tuple always be specific?
No, if the function returns a union of tuples, after destructuring the elements will represent a union of types of all possible variations.
type R = [number, null] | [null, string]; const [a, b]: R = [1, null]; // a: number | null // b: null | string
Can as const completely "fix" the tuple type without additional function type description?
No, as const fixes the values, but if the function is declared without a return type, TypeScript may infer the type wider than necessary. It's better to explicitly specify the return type.
function foo() { return [1, 'ok'] as const; } // foo(): readonly [1, "ok"]
Do overloads with different return types guarantee a definite type of the result after destructuring?
Overloads help the compiler, but if the input parameter is unknown, the result will be a union of all variants. To precisely narrow the type, type guards must be used in the result handler.
function bar(x: number): string; function bar(x: boolean): number; function bar(x: any): any { return typeof x === 'number' ? 'str' : 123; } const r = bar(Math.random() > 0.5 ? true : 1); // r: number | string
A function returns either an array or an error, but does not explicitly describe this in the type signature. In the calling code, it is expected that the result is a number, and accessing result[1].toFixed(2) throws an error at runtime.
Pros:
Cons:
A function returns a strictly typed tuple with the type Result<T>, the result processing is based on destructuring and explicit runtime checking of the first element (error/null). The compiler guarantees that you can only access the result upon successful type narrowing.
Pros:
Cons: