Typescript Function with Generic Return Type

It’s important to pay attention to where the generic type parameters are declared and what scope they have. The type

type FuncGenericReturn = <T>() => T;

is a concrete type referring to a generic function. <T>() => T means: “a function whose caller specifies a type T and which returns a value of type T.” This is essentially impossible to implement safely. Imagine if you had such a function:

declare const funcGenericReturn: FuncGenericReturn;

Then you should be able to call it this way:

const someNumber: number = funcGenericReturn<number>(); 
const someString: string = funcGenericReturn<string>();

But of course at runtime those will both compile to

const someNumber = funcGenericReturn();
const someString = funcGenericReturn();

Meaning that funcGenericReturn() would just have to “know” at runtime that it should first return a number and then a string, based on type information which is erased before the JavaScript is generated. So properly implementing a FuncGenericReturn would require magical foreknowledge.

To reiterate: when you have a generic function, the generic type parameters are specified by the caller, not by the implementer. It’s true that sometimes the compiler will infer these type parameters so that the person writing the code doesn’t have to spell it out, but again, these inferences are happening at call time. Two different calls to the same generic function could end up having two different choices for the type parameters.


Let’s compare this to a different but related type definition:

type FuncConcreteReturn<T> = () => T;

Here, FuncConcreteReturn is a generic type referring to a concrete function. It would be more accurate to say that FuncConcreteReturn is not really a type; it’s more like a type operator which takes an input type T and produces an output type () => T.

For any particular type T, the type FuncConcreteReturn<T> is a concrete function type which takes no parameter and returns a value of type T. So a FuncConcreteReturn<string> is a function that takes no arguments and returns a string, while a FuncConcreteReturn<number> is a function that takes no arguments and returns a number. Note that FuncConcreteReturn<string> is a different type from FuncContreteReturn<number>, and neither of them are a FuncConcreteReturn because that’s not a valid type. So the following is valid:

const funcReturnsNumber: FuncConcreteReturn<number> = () => 1;
const funcReturnsString: FuncConcreteReturn<string> = () => "";

Again, funcReturnsNumber is not a generic function. It is a concrete function that always returns a number. And FuncConcreteReturn<T> is a generic type, where the value of T is chosen when the type is written out. Since these types are function types, the type T is chosen by the implementer of these functions, and not by the caller.


By the way, the relationship between a generic function type like

type G = <T, U>(t: T, u: U) => [T, U]

and a generic type like

type H<T, U> = (t: T, u: U) => [T, U]

is that any instance of the former will be an instance of the latter, but not vice versa. This means that if you did have a FuncGenericReturn, you could assign it to a value of type FuncConcreteReturn<string> or a FuncConcreteReturn<number>:

const fn: FuncConcreteReturn<number> = funcGenericReturn; // okay
const fs: FuncConcreteReturn<string> = funcGenericReturn; // okay

Or, for the G and H types above, you could do this:

const g: G = <T, U>(t: T, u: U) => [t, u];
g("a", 1); // okay
g(1, "a"); // okay

const h1: H<string, number> = g; // okay
h1("a", 1); // okay
h1(1, "a"); // error

const h2: H<number, string> = g; // okay
h2(1, "a"); // okay
h2("a", 1); // error

Okay, I hope that gives you some understanding on the difference between generic functions and generic types. Good luck!

Playground link to code

Leave a Comment