Understanding infer in TypeScript
Tags: typescript, webdev
Published: 2021-10-13 14:55:50

**Written by [Simohamed Marhraoui](https://blog.logrocket.com/author/simohamedmarhraoui/) ✏️**

We’ve all been in situations where we used a library that had been typed sparingly. Take the following third-party function, for example:

```typescript
function describePerson(person: {
  name: string;
  age: number;
  hobbies: [string, string]; // tuple
}) {
  return `${person.name} is ${person.age} years old and love ${person.hobbies.join(" and  ")}.`;
}
```

If the library doesn’t provide a standalone type for the `person` argument of `describePerson`, defining a variable beforehand as the `person` argument would not be inferred correctly by TypeScript.

```typescript
const alex = {
  name: 'Alex',
  age: 20,
  hobbies: ['walking', 'cooking'] // type string[] != [string, string]
}

describePerson(alex) /* Type string[] is not assignable to type [string, string] */
```

TypeScript will infer the type of `alex` as `{ name: string; age: number; hobbies: string[] }` and will not permit its use as an argument for `describePerson`.

And, even if it did, it would be nice to have type checking on the `alex` object itself to have proper autocompletion. We can easily achieve this, thanks to the `infer` keyword in TypeScript.

```typescript
const alex: GetFirstArgumentOfAnyFunction<typeof describePerson> = {
  name: "Alex",
  age: 20,
  hobbies: ["walking", "cooking"],
};

describePerson(alex); /* No TypeScript errors */ 
```

The `infer` keyword and conditional typing in TypeScript allows us to take a type and isolate any piece of it for later use.

## The no-value `never` type

In TypeScript, `never` is treated as the “no value” type. You will often see it being used as a dead-end type. A union type like `string | never` in TypeScript will evaluate to `string`, discarding `never`.

To understand that, you can think of `string` and `never` as mathematical sets where `string` is a set that holds all string values, and `never` is a set that holds no value ([∅ set](https://en.wikipedia.org/wiki/Empty_set)). The union of such two sets is obviously the former alone.

By contrast, the union `string | any` evaluates to `any`. Again, you can think of this as a union between the `string` set and the [universal set](https://en.wikipedia.org/wiki/Universal_set) (U) that holds all sets, which, to no one’s surprise, evaluates to itself.

This explains why `never` is used as an escape hatch because, combined with other types, it will disappear.

## Using conditional types in TypeScript

Conditional types modify a type based on whether or not it satisfies a certain constraint. It works similarly to ternaries in JavaScript.

### The `extends` keyword

In TypeScript, constraints are expressed using the `extends` keyword. `T extends K` means that it’s safe to assume that a value of type `T` is also of type `K`, e.g., `0 extends number` because `var zero: number = 0` is type-safe.

Thus, we can have a generic that checks whether a constraint is met, and return different types.

`StringFromType` returns a literal string based on the primitive type it receives:

```typescript
type StringFromType<T> = T extends string ? 'string' : never

type lorem = StringFromType<'lorem ipsum'> // 'string'
type ten = StringFromType<10> // never
```

To cover more cases for our `StringFromType` generic, we can chain more conditions exactly like nesting ternary operators in JavaScript.

```typescript
type StringFromType<T> = T extends string
  ? 'string'
  : T extends boolean
  ? 'boolean'
  : T extends Error
  ? 'error'
  : never

type lorem = StringFromType<'lorem ipsum'> // 'string'
type isActive = StringFromType<false> // 'boolean'
type unassignable = StringFromType<TypeError> // 'error'
```

### Conditional types and unions

In the case of extending a union as a constraint, TypeScript will loop over each member of the union and return a union of its own:

```typescript
type NullableString = string | null | undefined

type NonNullable<T> = T extends null | undefined ? never : T // Built-in type, FYI

type CondUnionType = NonNullable<NullableString> // evalutes to `string`
```

TypeScript will test the constraint `T extends null | undefined` by looping over our union, `string | null | undefined`, one type at a time.

You can think of it as the following illustrative code:

```typescript
type stringLoop = string extends null | undefined ? never : string // string

type nullLoop = null extends null | undefined ? never : null // never

type undefinedLoop = undefined extends null | undefined ? never : undefined // never

type ReturnUnion = stringLoop | nullLoop | undefinedLoop // string
```

Because `ReturnUnion` is a union of `string | never | never`, it evaluates to `string` (see explanation above.)

You can see how abstracting the extended union into our generic allows us to create the built-in `Extract` and `Exclude` utility types in TypeScript:

```typescript
type Extract<T, U> = T extends U ? T : never
type Exclude<T, U> = T extends U ? never : T
```

### Conditional types and functions

To check whether a type extends a certain function shape, [the](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/ban-types.md#:~:text=Avoid%20the%20Function,the%20new%20keyword) `[Function](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/ban-types.md#:~:text=Avoid%20the%20Function,the%20new%20keyword)` [type must not be used](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/ban-types.md#:~:text=Avoid%20the%20Function,the%20new%20keyword). Instead, the following signature can be used to extend all possible functions:

```typescript
type AllFunctions = (…args: any[]) => any
```

`…args: any[]` will cover zero and more arguments, while `=> any` would cover any return type.

## Using `infer` in TypeScript

The `infer` keyword compliments conditional types and cannot be used outside an `extends` clause. `Infer` allows us to define a variable within our constraint to be referenced or returned.

Take the built-in TypeScript `ReturnType` utility, for example. It takes a function type and gives you its return type:

```typescript
type a = ReturnType<() => void> // void
type b = ReturnType<() => string | number> // string | number
type c = ReturnType<() => any> // any
```

It does that by first checking whether your type argument (`T`) is a function, and in the process of checking, the return type is made into a variable, `infer R`, and returned if the check succeeds:

```typescript
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
```

As previously mentioned, this is mainly useful for accessing and using types that are not available to us.

### React prop types

In React, we often need to access prop types. To do that, React offers a utility type for accessing prop types powered by the `infer` keyword called `ComponentProps`.

```typescript
type ComponentProps<
  T extends keyof JSX.IntrinsicElements | JSXElementConstructor<any>
> = T extends JSXElementConstructor<infer P>
  ? P
  : T extends keyof JSX.IntrinsicElements
  ? JSX.IntrinsicElements[T]
  : {}
```

After checking that our type argument is a React component, it infers its props and returns them. If that fails, it checks that the type argument is an `IntrinsicElements` (`div`, `button`, etc.) and returns its props. If all fails, it returns `{}` which, in TypeScript, means “any non-null value”.

## `Infer` keyword use cases

Using the `infer` keyword is often described as unwrapping a type. Here are some common uses of the `infer` keyword.

### Function’s first argument:

This is the solution from our first example:

```typescript
type GetFirstArgumentOfAnyFunction<T> = T extends (
  first: infer FirstArgument,
  ...args: any[]
) => any
  ? FirstArgument
  : never

type t = GetFirstArgumentOfAnyFunction<(name: string, age: number) => void> // string
```

### Function’s second argument:

```typescript
type GetSecondArgumentOfAnyFunction<T> = T extends (
  first: any,
  second: infer SecondArgument,
  ...args: any[]
) => any
  ? SecondArgument
  : never

type t = GetSecondArgumentOfAnyFunction<(name: string, age: number) => void> // number
```

### Promise return type

```typescript
type PromiseReturnType<T> = T extends Promise<infer Return> ? Return : T

type t = PromiseReturnType<Promise<string>> // string 
```

### Array type

```typescript
type ArrayType<T> = T extends (infer Item)[] ? Item : T

type t = ArrayType<[string, number]> // string | number
```

## Conclusion

The `infer` keyword is a powerful tool that allows us to unwrap and store types while working with third-party TypeScript code. In this article, we explained various aspects of writing robust conditional types using the `never` keyword, `extends` keyword, unions, and function signatures.

---

## Writing a lot of TypeScript? [Watch the recording](https://blog.logrocket.com/typescript-meetup-recap/?utm_source=social&utm_medium=organic&utm_campaign=21Q3_WB_TS-Tech-Meetup0930#utm_source=social&utm_medium=organic&utm_campaign=21Q3_WB_TS-Tech-Meetup0930) of our recent TypeScript meetup to learn about writing more readable code.

[![TypeScript meetup header](https://blog.logrocket.com/wp-content/uploads/2021/07/typescript-4-4-more-readable-code.png)](https://blog.logrocket.com/typescript-meetup-recap/?utm_source=social&utm_medium=organic&utm_campaign=21Q3_WB_TS-Tech-Meetup0930#utm_source=social&utm_medium=organic&utm_campaign=21Q3_WB_TS-Tech-Meetup0930)

TypeScript brings type safety to JavaScript. There can be a tension between type safety and readable code. Watch the recording for a deep dive on some new features of TypeScript 4.4.