Understanding the TypeScript `infer` keyword

In TypeScript, there are some types that are composed of multiple other types. We can refer to them as compound types. A promise for example, is a compound type because it’s composed of two types. The first being the Promise itself, and the second being the type of value that the promise holds.

A function is another example of a compound type because it consists of three different types. The first being the function itself, the second being the type of arguments that the function accepts, and the third being the type of result that the function returns.

And as a final example, we also have the array, which is composed of two types: The array itself and the type of values that the array holds.

So what does this have to do with the infer keyword? Well the infer keyword is what we would use if we wanted to determine one of the types of a compound type. So for instance, if we wanted to determine the type of value that is held by a promise, or the type of result that a function returns, we could use the infer keyword to do so.

Let's take the promise as an example and let's create a TypeScript utility that would extract the type of value of a given promise. Such a utility might look something like this:

type Unpromisify<P> = P extends Promise<infer V> ? V : never

Now let’s break it down. With the first part of the statement...

type Unpromisify<P> =

What we are saying is that the utility is called ‘Unpromisify’ and that it accepts an unknown type called P, as an argument. This is similar to how we can have a function called Unpromisify that accepts one argument called P.

function Unpromisify(P) {...}

The difference, however, is that the typescript utility will accept a type as an argument whereas the function would accept a value as an argument.

Now with the second part of the statement…

= P extends Promise<infer V> ? V : never

What we are saying is that if P is a Promise of some value whose type we will declare to be V , then infer what V is and return it to us.

You see, the statement...

P extends Promise<infer V> ? V : never

is a conditional statement that takes the form of

condition ? trueExpression : falseExpression

As in...

<If condition is true> ? <Then do this> : <otherwise do this>

In our case, for the condition to evaluate as true, P must extend a Promise that holds a type of value that we will declare to be V. Once this condition is true, then it should return the inferred type of V, otherwise return the never type or no type.

Think of V as being somewhat like the X in an algebraic equation. Something like saying...

If 7 is equal to 5 + X, then tell me what X is

We are somewhat asking the same thing with the infer keyword.

If P extends Promise<infer V> ? then tell me what V is : otherwise tell me nothing

In this case, the infer keyword is the thing that designates that V is the equivalent of X (i.e., that it's the thing that we need to deduce). P we know because P is an argument that we specified. But V we don't yet know because we have not declared it anywhere. So by placing the infer keyword in front on V, what we are doing is declaring that V is a container for an unknown type that we want to get a hold of. It's somewhat like declaring a variable within a function:

const Unpromisify = (P) =>
  P === Promise<var V>
    ? V
    : undefined

or

function Unpromisify = (P) {
  var V
  if(P instanceof Promise) {
    V = extractPromiseValueOf(P)
  }
  return V 
}

Now notice that in my description of the second half of the Unpromisify utility, I said that what we are saying is that if P is a Promise of some value whose type we will DECLARE to be V, then infer what V is and return it to us. Well my use of the term DECLARE here is intentional because that is essentially what we are using the infer keyword to do. It's a way of declaring a type variable so to speak, similar to declaring a variable in javascript.

It's only for conditionals

Now there is one main thing to know about the infer keyword, and it's that just like the algebraic equation we saw above, the infer keyword must be used within a conditional statement, as it’s the only way to get an expression to evaluate to the type being inferred.

Take the following statement as an example...

type Unpromisify<P> = Promise<infer V>

This statement is useless to us if the aim is to get the type of V because the expression does not evaluate to V. Instead it evaluates to a promise of V. While we have declared V for inference, we are not grabbing a hold of it. To get V, we need an expression that can allow us to grab a hold of V and return it. Conditionals allow this by giving us the opportunity to evaluate to V either when the condition is true or when it’s false.

type Unpromisify<P> = P extends Promise<infer V> ? V : never

So in essence…

The infer keyword allows us to use a conditional statement to infer an unknown type.

or rather...

It's what we use to declare a type for inference within the body of a conditional statement

It's not restricted to compound types

Now at the beginning, I mentioned that we could use the infer keyword to determine one of the types of a compound type. This might have given the impression that the infer keyword could ONLY be used on compound types but that is not the case. The type that is being inferred doesn’t have to be part of a compound type. Compound types are just the most common use case and so it makes it easier to explain the intent and usage of the infer keyword. We could also use the infer keyword to infer on non compound types as well. The following is an example:

type Foo<P> = P extends infer V ? V : never

While it may seem redundant because it’s essentially the same as doing...

type Foo<P> = P

…it’s perfectly legal. With it, what we are saying is that if P extends some type that we will declare to be V, infer the type of V and return it. And because P will definitely extend something, the condition will evaluate to true, and so the type that P extends is what will be returned.

type Bar = Foo<typeof 3>
// Bar will end up being a type of number

Some more examples

Inferring the return type of a function

So now that we have somewhat of a grasp of what the infer keyword does, let’s look at some more examples of it. Let's say that we wanted to create a utility type that returns to us the return type of a given function, we could construct it as follows:

type ReturnType<F> = F extends (...args: any[]) => infer R ? R : never

Here we are saying that if F is a type of function that returns some result that we will declare to be of type R, then return R.

Inferring the type of a functions arguments

Now let's say that we wanted to get hold of the type of arguments that the function accepts. A utility that does exactly this, would look something like the following:

type Parameters<F> = F extends (...args: infer P) => any ? P : never

With this, what we are saying is that if F is a type of function that accepts arguments that we will declare to be of type P, then return the type of P.

Inferring on strings

Let's do something a little bit different and explore the realm of strings. Let's say we have a string that starts with a space character and we want to get the type of string that comes after the space... in other words, we want to be able to do this:

type S = StringAfterSpace<' Hello'> 
// S should resolve to a type of 'Hello' without the space in front of it

We could construct the StringAfterSpace utility as follows:

type SpaceChar = ' ' 
type StringAfterSpace<S> = S extends `${SpaceChar}${infer R}` ? R : never

Again, with this utility, what we are saying is that if S is a type of string (that we will declare to be R) that starts with a space character, then return R.

So there you have it. This is the infer keyword in a nutshell.