Return Different Types in TypeScript

// #generics#types#typescript // 16 comments

Conditional return types are a powerful feature of TypeScript that allow you to specify different return types for a function based on the type of the arguments. This can be useful when you want to enforce type safety and ensure that the return type matches the expected type.

For example, consider a function for a custom plus operator with two arguments. If the arguments are strings, the two strings are concatenated and returned. If the arguments are numbers, it adds the two numbers together and returns the sum.

function plus<T extends string | number>(a: T, b: T): T extends string ? string : number { if (typeof a === 'string' && typeof b === 'string') { return (a + b) as string; } if (typeof a === 'number' && typeof b === 'number') { return (a + b) as number; } throw new Error('Both arguments must be of the same type'); } const result1 = plus(1, 2); // result1 has type number const result2 = plus('Hello ', 'World'); // result2 has type string

In this code, the plus function takes two arguments of type T, which can be either a string or a number. The function then uses a conditional return type to specify that the return type should be a string if T extends string, and a number otherwise.

However, TypeScript has trouble correctly inferring the return type within the function implementation. The compiler reports errors on lines 3 and 7, although the return type is correctly inferred on lines 14 and 15 when the function is called.

TypeScript
TypeScript playground

The problem is that the type T is used in both the function signature and the conditional return type, which can lead to a circular reference error. To fix this, we need to use a separate type parameter R for the return type:

function plus<T extends string | number, R = T extends string ? string : number>(a: T, b: T): R { if (typeof a === 'string' && typeof b === 'string') { return (a + b) as R; } if (typeof a === 'number' && typeof b === 'number') { return (a + b) as R; } throw new Error('Both arguments must be of the same type'); } const result1 = plus(1, 2); // result1 has type number const result2 = plus('Hello ', 'World'); // result2 has type string

In this example, the R type parameter is used to specify the return type based on the conditional type. This avoids the circular reference error and allows the function to be correctly typed.

TypeScript
TypeScript playground