As Matt McCutchen points this is a limitation of ReturnType and in general conditional types and multiple overload signatures.

We can however construct a type that will return all overloaded return types for up to an arbitrary number of overloads:

function applyChanges1(input: string): number function applyChanges1(input: number): string function applyChanges1(input: number | string): number | string { return typeof input === "number" ? input.toString() : input.length } function applyChanges2(input: number): string function applyChanges2(input: string): number function applyChanges2(input: number | string): number | string { return typeof input === "number" ? input.toString() : input.length } type OverloadedReturnType<T> = T extends { (...args: any[]) : infer R; (...args: any[]) : infer R; (...args: any[]) : infer R ; (...args: any[]) : infer R } ? R : T extends { (...args: any[]) : infer R; (...args: any[]) : infer R; (...args: any[]) : infer R } ? R : T extends { (...args: any[]) : infer R; (...args: any[]) : infer R } ? R : T extends (...args: any[]) => infer R ? R : any type RetO1 = OverloadedReturnType<typeof applyChanges1> // string | number type RetO2 = OverloadedReturnType<typeof applyChanges2> // number | string

The version above will work for up to 4 overload signatures (whatever they may be) but can easily (if not prettily) be extended to more.

We can even get a union of possible argument types in the same way: