Advanced TypeScript: Infer Keyword
Welcome to the Infer Keyword lesson!
This lesson is shown as static text below. However, it's designed to be used interactively. Click the button below to start!
Sometimes we want our type definitions to "unpack" generic types. For example, we might want to write an
ArrayContents<T>type that tells us what type is inside an array. If we use it on a number array, likeArrayContents<Array<number>>, it should give usnumberback.TypeScript lets us write
ArrayContentsin a relatively simple way. We can index into the array type to get the type that's inside it. (Remember that there are two ways to do this. We can index with a literal number type, likeSomeArrayType[0], or we can doSomeArrayType[number]. Either method gives us the type inside of the array.)>
type ArrayContents<T extends Array<any>> = T[number];const n: ArrayContents<Array<number>> = 'one';n;Result:
type error: Type 'string' is not assignable to type 'number'.
>
type ArrayContents<T extends Array<any>> = T[number];const n: ArrayContents<Array<number>> = 1;n;Result:
1
That's one way of defining
ArrayContents. Now, let's try doing the same thing by using TypeScript'sinferkeyword. This might seem more verbose at first, but will introduce us to theinfersyntax. After that, we'll useinferto define another type that wouldn't be possible without this keyword.Here's the more complex version of
ArrayContents. It works just like the one above. We'll look at the code first, then discuss why it works.>
type ArrayContents<T> =T extends Array<infer ElementType>? ElementType: never;const n: ArrayContents<Array<number>> = 1;n;Result:
1
>
type ArrayContents<T> =T extends Array<infer ElementType>? ElementType: never;const n: ArrayContents<Array<number>> = 'one';n;Result:
type error: Type 'string' is not assignable to type 'number'.
We could've added a constraint to our generic type, but we didn't. With no constraint in place, we can use
ArrayContentson any type, even types that aren't arrays. In those cases we get the "false" side of the conditional type, which isnever.>
type ArrayContents<T> =T extends Array<infer ElementType>? ElementType: never;const n: ArrayContents<{name: string}> = 1;n;Result:
type error: Type '1' is not assignable to type 'never'.
Note the type error message:
nhas the typeneverbecause we took the "false" side of the conditional type.When we give
ArrayContentsan array, we get the "true" side of the conditional type becauseT extends Array<infer ElementType>is true. The critical piece here is theinferkeyword:infer ElementType. If we remove theinfer, we get a type error.>
type ArrayContents<T> =T extends Array<ElementType>? ElementType: never;const n: ArrayContents<Array<number>> = 1;n;Result:
type error: Cannot find name 'ElementType'.
Why doesn't that work? It's because we never actually declared
ElementType, so the compiler doesn't know what it is. This is similar to referencing a variable we haven't defined. If we don't declarexwithconst x =orfunction xor in some other way, then referencingxis an error.By using
infer ElementType, we're declaringElementType. But this kind of declaration is used for a very specific purpose. It only works inside the condition of a conditional type, and it can only infer generic type parameters. In that one very specific situation,infertells TypeScript to automatically infer the type parameter.>
type ArrayContents<T> =T extends Array<infer ElementType>? ElementType: never;const n: ArrayContents<Array<string>> = 'one';n;Result:
'one'
Once TypeScript has inferred
ElementTypefor us, we can reference it like any other type. In our case, the "true" side of our conditional type givesElementType, and our type works as expected.Remember that we started with an easier way to define
ArrayContents, without usinginfer. That makes ourinfer-basedArrayContentsa contrived example. Now we switch to an example that's impossible withoutinfer: theReturnTypeutility type. We usedReturnTypein an earlier lesson, but without understanding how it's implemented.Here's a working
OurReturnTypetype that's similar to TypeScript's built-inReturnType. We'll see the code first, then discuss why it works.>
type OurReturnType<Func> =Func extends (...args: any) => infer Return? Return: never;function add(x: number, y: number): number {return x + y;}- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
const n: OurReturnType<typeof add> = 123;n;Result:
123
- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
const n: OurReturnType<typeof add> = 'one two three';n;Result:
type error: Type 'string' is not assignable to type 'number'.
That type error shows us that the type of
nisnumber, which means thatOurReturnTypeworks!We've used
inferto infer the function's return type, like we did in theArrayContentstype. If you scroll up and compareOurReturnTypetoArrayContents, you'll see that they look very similar, except that the condition in the conditional type is different.The function type makes
OurReturnTypelook more complex, but the ideas are exactly the same. In our initial example, we inferred the array's element type. In this example, we inferred the function's return type.OurReturnTypeis a bit more readable than the actual TypeScriptReturnType. We renamed its type parameters, and we simplified it by removing a type constraint that wasn't important when explaininginfer. If you're interested, here's the version that actually ships with TypeScript.>
type ReturnType<T extends (...args: any) => any> =T extends (...args: any) => infer R ? R : any;In the code problem below, you'll write a
SetContentstype. It's very similar to theArrayContentstype, but it works on sets instead of arrays.Here's a code problem:
Finish the type below, which gets the type of a set's elements. You'll need to use a conditional type and the
inferkeyword.type SetContents<T> =T extends Set<infer ElementType>? ElementType: never;const n: SetContents<Set<number>> = 123;const s: SetContents<Set<string>> = 'a string';[n, s];- Goal:
[123, 'a string']
- Yours:
[123, 'a string']