Execute Program

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, like ArrayContents<Array<number>>, it should give us number back.

  • TypeScript lets us write ArrayContents in 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, like SomeArrayType[0], or we can do SomeArrayType[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'.Pass Icon
  • >
    type ArrayContents<T extends Array<any>> = T[number];
    const n: ArrayContents<Array<number>> = 1;
    n;
    Result:
    1Pass Icon
  • That's one way of defining ArrayContents. Now, let's try doing the same thing by using TypeScript's infer keyword. This might seem more verbose at first, but will introduce us to the infer syntax. After that, we'll use infer to 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:
    1Pass Icon
  • >
    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'.Pass Icon
  • We could've added a constraint to our generic type, but we didn't. With no constraint in place, we can use ArrayContents on any type, even types that aren't arrays. In those cases we get the "false" side of the conditional type, which is never.

  • >
    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'.Pass Icon
  • Note the type error message: n has the type never because we took the "false" side of the conditional type.

  • When we give ArrayContents an array, we get the "true" side of the conditional type because T extends Array<infer ElementType> is true. The critical piece here is the infer keyword: infer ElementType. If we remove the infer, 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'.Pass Icon
  • 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 declare x with const x = or function x or in some other way, then referencing x is an error.

  • By using infer ElementType, we're declaring ElementType. 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, infer tells 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'Pass Icon
  • Once TypeScript has inferred ElementType for us, we can reference it like any other type. In our case, the "true" side of our conditional type gives ElementType, and our type works as expected.

  • Remember that we started with an easier way to define ArrayContents, without using infer. That makes our infer-based ArrayContents a contrived example. Now we switch to an example that's impossible without infer: the ReturnType utility type. We used ReturnType in an earlier lesson, but without understanding how it's implemented.

  • Here's a working OurReturnType type that's similar to TypeScript's built-in ReturnType. 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:
    123Pass Icon
  • 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'.Pass Icon
  • That type error shows us that the type of n is number, which means that OurReturnType works!

  • We've used infer to infer the function's return type, like we did in the ArrayContents type. If you scroll up and compare OurReturnType to ArrayContents, you'll see that they look very similar, except that the condition in the conditional type is different.

  • The function type makes OurReturnType look 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.

  • OurReturnType is a bit more readable than the actual TypeScript ReturnType. We renamed its type parameters, and we simplified it by removing a type constraint that wasn't important when explaining infer. 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 SetContents type. It's very similar to the ArrayContents type, 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 infer keyword.

    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']Pass Icon