Notes on TypeScript: Conditional Types
Tags: typescript, types
Published: 2019-02-17 13:39:11

---
title: Notes on TypeScript: Conditional Types
published: true
description: Better understanding conditional types in TypeScript
tags: typescript, types
series: Notes on TypeScript
---

### Introduction

These notes should help in better understanding `TypeScript` and might be helpful when needing to lookup up how leverage TypeScript in a specific situation. All examples are based on TypeScript 3.2.

### Conditional Types

In the last part of the "Notes on TypeScript" series we focused on understanding the basics of type level programming. Before we continue with more advanced type level examples in TypeScript, it might be a good idea to cover another topic first.
In this part of the series we will try to better understand what conditional types are and how we can leverage them when working with TypeScript in an existing application.

In it's most basic form a conditional type is a sort of ternary expression for types. To get a better idea, let's look at a low level example.

```js
type S = string extends any ? "string" : never;

/*
type S = "string"
*/
```

The [release notes for TypeScript 2.8](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html) have a definition for conditional types:

_"A conditional type selects one of two possible types based on a condition expressed as a type relationship test"_

So if we look at our initial example, we might think about conditional types like the following:

```js
T extends U ? X : Y
```

This means if type `T` extends `U`, then the conditional type will be resolved to `X` else it will be resolved to `Y`. This might still sound abstract at first, but we will see the usefulness of this concept in the following examples. Again, we will write some common functions, that will help us display the why and what of conditional types.

```js
type Exclude<T, U> = T extends U ? never : T;
```

If we look at the above type definition, we can see that we are using a ternary expression to define the return type. `never` is a low level type, it can not be refined any further and we can leverage the fact that `never` indicates that this value should never exist, meaning we can filter out a type if it is `never`.

If we can write an `Exclude` type, we can also define an `Include` type.

```js
type Include<T, U> = T extends U ? T : never;
```

So just by looking at the above definition we can notice that we flipped the returned type. Interestingly `Include` can be replaced with `Extract` that is a predefined conditional type in TypeScript, the same goes for our own defined `Exclude`, which is also predefined.

Now, that we are gaining a better understanding conditional types, let's use what we have learned so far to build a `NonNullable` conditional type, that excludes any `null` or `undefined` types from a provided type.

```js
type NonNullable<T> = Exclude<T, null | undefined>;

// Test NonNullable
type TestNonNullable = NonNullable<string | null | number | undefined>;

/*
type TestNonNullable = string | number;
*/
```

In the above example we leveraged our previously defined `Exclude` type, but we can also write it in a more explicit manner.

```js
type NonNullable<T> = T extends null | undefined ? never : T;

// Test NonNullable
type TestNonNullable = NonNullable<string | null | number | undefined>;

/*
type TestNonNullable = string | number;
*/
```

This is exactly how the predefined `NonNullable` is implemented in TypeScript. We check if the provided type extends `null` or `undefined` and either return a `never` type or the type itself.

It might be interesting to take a step back and try to better understand what happened when we tested `NonNullable` type. The following applied when we ran the previous example:

```js
type TestNonNullable =
  | NonNullable<string>
  | NonNullable<null>
  | NonNullable<number>
  | NonNullable<undefined>;

/*
type TestNonNullable = string | number;
*/
```

We have been implementing existing conditional types so far. To further our understanding of the topic, let's look at an example, where we might want to remove all nullable properties from a type and create a new type that only expects non-nullable types.

```js
type RemoveUndefinable<Type> = {
  [Key in keyof Type]: undefined extends Type[Key] ? never : Key
}[keyof Type];

type RemoveNullableProperties<Type> = {
  [Key in RemoveUndefinable<Type>]: Type[Key]
};

type TestRemoveNullableProperties = RemoveNullableProperties<{
  id: number;
  name: string;
  property?: string;
}>;

/*
type TestRemoveNullableProperties = {
  id: number;
  name: string;
};
*/
```

The `RemoveNullableProperties` type removes all optional types as they might be undefined. Mapped and conditional types can be combined to build advanced types as seen in the above example.

### Infer and Type Interference

Before we continue implementing more conditional types, let's try to better understand how we can leverage type interference with conditional types. Using the `infer` keyword, we can tell TypeScript to try to infer the type. Let's take a look at a couple examples, where leveraging `infer` can help us.

```js
type GetFunctionArgumentTypes<Type> = Type extends (a: infer U) => void
  ? U
  : never;

type TestGetFunctionArgumentTypesA = GetFunctionArgumentTypes<
  (a: number) => void
>;

/*
type GetFunctionArgumentTypes = number;
*/

type GetPropertyTypes<Type> = Type extends {a: infer U, b: infer U} ? U : never;

type TestGetPropertyTypes = GetPropertyTypes<{a: number, b: string[]}>;

/*
type TestGetPropertyTypes = number | string[];
*/
```

Looking at the above examples, we notice that we can place the `infer` keyword at the position where we want the type  to be inferred. Let's see if we can infer the return type of a function f.e.

```js
type GetReturnType<Type> = Type extends (...a: any[]) => infer R ? R : any;

type TestGetReturnType = GetReturnType<(a: number) => number[]>;

/*
type TestGetReturnType = number[];
*/
```

Our `GetReturnType` can infer the return type of a provided function. This is a similar implementation to the TypeScript provided `ReturnType`. You can read more about [ReturnType](https://dev.to/busypeoples/notes-on-typescript-returntype-3m5a) in one of the previous "Notes on TypeScript" write-ups.

Finally we can extend our previous `GetReturnType` to infer the instance type of a constructor function.

```js
type GetInstanceType<T> = T extends new (...args: any[]) => infer R ? R : any;

type TestGetInstanceType = GetInstanceType<new (a: number) => number | string | undefined>;

/*
type TestGetReturnType = number | string | undefined;
*/
```

Again, this implementation is similar to the TypeScript provided conditional type `InstanceType`.

We should have a good understanding of conditional types and how to leverage them when working with TypeScript at this point. The knowledge gained in this write-up should help us to build more advanced type level programming examples in one of of the upcoming "Notes on TypeScript".


If you have any questions or feedback please leave a comment here or connect via Twitter: [A. Sharif](https://twitter.com/sharifsbeat)