Conditional Return Types: How to Return the Right Type
Tags: typescript, tutorial, javascript, programming
Published: 2023-03-16 13:31:48

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.

```ts
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.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8jcyyghc0u8hf72yb7di.jpg)<figcaption>[TypeScript playground](https://www.typescriptlang.org/play?#code/GYVwdgxgLglg9mABABwDYgM4B4AqiCmAHlPmACYaIZQBOMYA5ogD6JggC2ARvjQHwAKAIYAuRDgA0iLmJwBKWQWKkKVWvSYB+NXUaIx7br0QBvALAAoRIhjBEAqAE9k+OHaGIAvN8QByaroMvogAZCGITi5u0l4+-uqMvnKmltbWNPhQIDRIwogA1NLJQpQBGgDcqYgAvpZVtvaRru6xnn6GPDTBYRHOzTHebb4dvEkpVmkZWTn2HoVcxZQjNJUTtRZVUAAWNHAA7mz4BwCiNLs0Ar4AQnDbiEI0DJykUJQcmFDS+IjR298YQg43yaSVW60sEAQ1EQGQwIFQUAAjF4UOgMAJEVIAExycqIAD0+Jh+DhCORWxKvRcbE4nQhUM+sPhUCxKLQmEuAAl8KhUHA-FJfAB1OA0VBkUEEolMhGsimUJo6DRAA)</figcaption>

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:

```ts
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. 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wjo7utbfua665sy6zaer.jpg)<figcaption>[TypeScript playground](https://www.typescriptlang.org/play?#code/GYVwdgxgLglg9mABABwDYgM4B4AqiCmAHlPmACYaIZQBOMYA5ogD6JggC2ARvjQDSIASogC8iPERLlK1Oo0QB+KrXpMAXG048aAPgAUAQw04BXYwEoNwgN4BYAFCJEMYIj1QAnsnxxXB0SJiAOSyqkGIAGQRiJ7evohcAcGhjEHmiHaOTog0+FAgNEiGiADUCekGlIIA3A5OAL4Odc6u7l4+fkmIQezcvOFRMe3xiYHBvdppGc1OufmFbv5lXBVVtVmN9s1QABY0cADubPhHAKI0+zR6QQBCcLuIBjQMnKRQlByYUAn4iPG7vwwBg4v1i+DS602DggCGoOXwGBAqCgAEZRCh0Bg9CiBAAmczVRAAeiJ8MRyLRO0qQ28mj6NGhsO+uXJUFx6LQmGuAAl8KhUHBugIggB1OA0VBkCHE0kspFsxBUyhg5RyBhAA)</figcaption>

---
I hope you found this post helpful. If you have any questions or comments, feel free to leave them below. If you'd like to connect with me, you can find me on [LinkedIn](https://www.linkedin.com/in/christian-zirkel/) or [GitHub](https://github.com/zirkelc). Thanks for reading!