Notes on TypeScript: Type Level Programming Part 1
Tags: typescript, types
Published: 2019-02-09 17:52:31

---
title: Notes on TypeScript: Type Level Programming Part 1
published: true
description: Type Level Programming 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.

### Type Level

We will define a couple of useful types, that we can leverage when working with TypeScript. As this is intended as an introductory into the topic, we will focus on writing our own types and then replace them with the existing implementations in TypeScript. This approach will help us explore advanced features as well as learn how they are implemented along the way. In the next part of this series we will leverage the knowledge gained to build more custom types.

#### Readonly

There are time where we want to ensure that a value is not overridden. This can be achieved by using `readonly`, for example we might want to guarantee that a specific object property should only be read only, we can do so in TypeScript:

```js
type User = {
  readonly id: number;
  name: string;
}
```

But there are times where we want to ensure that a type is immutable, not only specific properties. To solve this we can write our own `MakeReadOnly` type definition that accepts a type and ensures that that the newly defined type is readonly.

```js
type MakeReadOnly<Type> = {readonly [key in keyof Type ]: Type[key]};
// Test MakeReadOnly
type ReadOnlyUser =  MakeReadOnly<User>;

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

The `MakeReadOnly` type we defined above is strictly for learning purposes as TypeScript offers the `Readonly` that we can leverage to achieve the same outcome like in the previous example.

```js
type ReadOnlyUser =  Readonly<User>;
```

#### Partial/Required

There are situations where we might be wanting to transform a given object or need to ensure that all properties are required but are expecting an object with optional properties.
We can write our own functionalities to ensure that we can transform a type definition from partial to required and vice versa.
Next, let's write some types.

```js
type MakePartial<Type> = { [key in keyof Type]?: Type[key] };
type MakeRequired<Type> = { [key in keyof Type]-?: Type[key] };

// Test MakePartial and MakeRequired
type BlogPost = {
  id: number;
  title: string;
  description?: string;
}

type PartialBlogPost = MakePartial<BlogPost>;

/*
type PartialBlogPost {
  id?: number | undefined;
  title?: string / undefined;
  description?: string / undefined;
}
*/

type RequiredBlogPost = MakeRequired<BlogPost>;

/*
type RequiredBlogPost {
  id: number;
  title: string;
  description: string;
}
*/
```

There is one interesting aspect that we should note here, using `-?` in our `MakeRequired` ensures that we remove any optionals, we can use `+` or `-` to gain control over the type modifier, check [this answer](https://stackoverflow.com/a/52417260) for more information.

Again, our above defined `MakePartial` and `MakeRequired` can be replaced by TypeScript's own `Partial` and `Required`, which will lead to the same results as our previous example.

```js
type PartialBlogPost = Partial<BlogPost>;

/*
type PartialBlogPost {
  id?: number | undefined;
  title?: string / undefined;
  description?: string / undefined;
}
*/

type RequiredBlogPost = Required<BlogPost>;

/*
type RequiredBlogPost {
  id: number;
  title: string;
  description: string;
}
*/
```

#### Pick, Exclude and Omit

There are situations where we need to create a type from an existing type and might need to pick or remove some of the defined properties.

Before implement our `MakePick` type, let's see how we can extract the intersecting keys between two types.

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

// Test MakeIntersect
type User = {
  id: number;
  name: string;
  title: string;
}

type Profile = {
  id: number;
  title: string;
  url: string;
}

type UserProfile = MakeIntersect<keyof User, keyof Profile>;

/*
type UserProfile = "id" | "title";
*/
```

Now that we know how to find the intersecting keys between two provided types, we can pick the keys for a provided type.

```js
type ProfileSelectedKeys = MakeIntersect<
  keyof Profile,
  "id" | "nonExistingKey" | "title"
>;

/*
type ProfileSelectedKeys = "id" | "title";
*/
```

This also means we can a write a type, that can pick the intersected keys and return a new type definition, now.

```js
type MakePick<Type, Keys extends keyof Type> = { [Key in Keys]: Type[Key] };

// Test MakePick

type NewProfile = MakePick<Profile, "id" | "title">;

/*
type NewProfile = {
  id: number;
  title: string;
}
*/
```

Sometimes we want to exclude specific properties when overriding an existing type, so our next step is define a `MakeExclude` type, that returns all the keys that can't be found.

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

// Test MakeExclude
type NonExistentKeys = MakeExclude<keyof User, keyof Profile>;

/*
type NonExistentKeys = "name";
*/
```

The property `name` doesn't exist in `Profile`, which means our newly defined type is `type NonExistentKeys = "name"`.

We can replace our previously defined `MakePick` and `MakeExclude` with `Pick` and `Exclude` that come with TypeScript.

```js
// Test MakeExclude
type NonExistentKeys = Exclude<keyof User, keyof Profile>;

/*
type NonExistentKeys = "name";
*/

type NewProfile = Pick<Profile, "id" | "title">;

/*
type NewProfile = {
  id: number;
  title: string;
}
*/
```

Finally, we might want to omit properties from a type definition. TypeScript currently doesn't offer an `Omit` type, but we can implement our own using `Exclude` and `Pick`. Also, in the very [first part of the "Notes on TypeScript"](https://dev.to/busypeoples/notes-on-typescript-pick-exclude-and-higher-order-components-40cp) series we implemented our own `Omit` already.

```js
type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;

// Test Omit
type NewProfile = Omit<Profile, "title">;
/*
type NewProfile = {
  id: number;
  url: string;
}
*/
```


We should have a basic understanding of how to leverage type level programming in TypeScript now.

In the next part we will build more advanced types that will help with the daily work when using TypeScript.

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