Notes on TypeScript: Type Level Programming Part 2
Tags: typescript, types
Published: 2019-03-09 18:07:52

---
title: Notes on TypeScript: Type Level Programming Part 2
published: true
description: Type Level programming in TypeScript explained
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 Programming

In this part of the series we will continue with learning more about type level programming in TypeScript. It might be interesting to note, that to better understand this post, a basic understanding of conditional types as well as mapped and lookup types can be helpful.
You can find the following posts here:

* [Notes on TypeScript: Type Level Programming Part 1](https://dev.to/busypeoples/notes-on-typescript-type-level-programming-in-typescript-part-1-i57)

* [Notes on TypeScript: Conditional Types](https://dev.to/busypeoples/notes-on-typescript-conditional-types-4bh)

* [Notes on TypeScript: Mapped Types and Lookup Types](https://dev.to/busypeoples/notes-on-typescript-mapped-types-and-lookup-types-i36)

### Examples

In this part of the "Notes on TypeScript" series we will be writing some examples to solidify our existing knowledge on the topic.

Let's write an abstraction for defining an object type first.

```js
export type ObjectType<Type> = { [k: string]: Type };
```

Now we can use this newly created to define an object, where we might not know all the properties, but know about the property types.

```js
const o1: ObjectType<string> = {};
const o2: ObjectType<string> = { name: "Test ObjectType" };
//const o3: ObjectType<string> = {name: 1}; // Error! Type 'number' is not assignable to type 'string'.
```

We can take the same above approach and define an array type definition.

```js
type ArrayType<Type> = { [k: number]: Type };

const a1: ArrayType<string> = [];
const a2: ArrayType<string> = ["Test 1", "Test2"];
// const a3 : ArrayType<string> = [1, 2]; // Error! Type 'number' is not assignable to type 'string'.
```

The above definition is only for demonstration purposes, as the built-in `Array` type already covers this case. But the example shows how we can create our own types and type helpers.

There might be a situation, where we might be interested in converting an existing object by changing the property type definitions.

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

type User = {
  id: number;
  name: string;
};

type UserPropertiesToString = MakeRecord<User, string>;

/*
  type UserPropertiesToString = {
    id: string;
    name: string;
  }
*/

const r1: UserPropertiesToString = { id: "1", name: "Test" };
// const r2: UserPropertiesToString = { id: 1, name: "Test" }; // Error! Type 'number' is not assignable to type 'string'.
```

Let's start build some more advanced examples next. With type level programming we can extend TypeScript to build more convenience types.

We could write our own `Omit` type via leveraging the existing `Pick` type.

```js
type Omit<Type, Keys> = Pick<Type, Exclude<keyof Type, Keys>>;

type UseWithOnlyId = Omit<User, "name">;

/*
  type UserWithOnlyId = {
    id: number;
  };
*/
```

The interesting aspect here, is that we can build more types on top of other types we created. For example we might want to be able to transform an existing type.

```js
type MakeOverwrite<T, U> = Omit<T, keyof T & keyof U> & U;
```

Now we can use `MakeOverwrite` to override an existing type. Let's see how this works via an example.

```js
type UserWithStringId = MakeOverwrite<
  User,
  {
    id: string;
  }
>;

const userWithStringId: UserWithStringId = {
  id: "1",
  name: "Test"
};

// const userWithStringId2 : UserWithStringId = {
//   id: 1,
//   name: "Test"
// };
// Error! Type 'number' is not assignable to type 'string'.
```

Or we might want to extend as well as override an existing type.

```js
type UserWithPoints = MakeOverwrite<
  User,
  {
    id: string;
    points: number;
  }
>;

const userWithPoints: UserWithPoints = {
  id: "1",
  name: "Test",
  points: 0
};

// const userWithPoints: UserWithPoints = {
//   id: "1",
//   name: "Test",
// };
// Error! Property 'points' is missing.
```

We can see how we can keep extending the types. Let's see another interesting helper type we might want to implement when working with collections.

```js
type GetHeadType<Type> = Type extends [infer H, ...any[]] ? H : never;
```

Now we might want to infer the type of the first item in an array. By using `GetHeadType` we can infer the type of that initial item.

```js
type HeadType = GetHeadType<["Test", 1, { id: 2 }]>; // type HeadType = "Test"
type HeadType2 = GetHeadType<[1, { id: 2 }, "Test"]>; // type HeadType2 = 1
type HeadType3 = GetHeadType<[{ id: 2 }, 1, "Test"]>; // type HeadType3 = {id: 2;}
```

Using conditional types, we can write logical types.

```js
type Not<Type extends boolean> = Type extends true ? false : true;
```

We might use `Not` to ensure that one type is the opposite of a provided type.

```js
type TestNot = Not<true>; // type TestNot = false;
type TestNot2 = Not<false>; // type TestNot = true;
```

Let's build some more examples to solidify our knowledge.

There might be a situation where we might want to convert all type properties to strict.

```js
type MakeStrict<Type> = { [Key in keyof Type]-?: Type[Key] };

type Product = {
  id: number;
  amount: number;
  name: string;
  description: string;
  locationId?: number;
};

type StrictProduct = MakeStrict<Product>;

/*
  type StrictProduct = {
    id: number;
    amount: number;
    name: string;
    description: string;
    locationId: number;
  };
*/
```

Via using `-?` we can tell TypeScript to remove the optional parameter.
The exact opposite of strict, making a type optional, can be achieved by using `+?`.

```js
type MakeOptional<Type> = { [Key in keyof Type]+?: Type[Key] };

type OptionalProduct = MakeOptional<Product>;

/*
  type StrictProduct = {
    id?: number;
    amount?: number;
    name?: string;
    description?: string;
    locationId?: number;
  };
*/
```

If we need to remove all optional properties, we could write our own type that removes all optional properties.

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

type OnlyRequired<T> = Pick<T, RequiredKeys<T>>;

type OnlyRequiredProduct = OnlyRequired<Product>;

/*
  type OnlyRequiredProduct = {
    id: number;
    amount: number;
    name: string;
    description: string;
  };
*/
```

The interesting part here is that `{} extends {id?: number}` is true but that  `{} extends {id: number}}` is false, which enables to identify if a property is optional or not.

```js
type OptionalExtendsTest = {} extends {id?: number} ? true: false; // type OptionalExtendsTest = true
type OptionalExtendsTest2 = {} extends {id: number} ? true: false; // type OptionalExtendsTest = false
```

There might be a situation where we want to find out which properties are of a specific type.

```js
type MakeKeysByType<Type extends {}, SearchType> = {
  [Key in keyof Type]-?: Type[Key] extends SearchType ? Key : never
}[keyof Type];

type ProductPropsByTypeNumber = MakeKeysByType<Product, number>;
// type ProductPropsByTypeNumber = "id" | "amount"
```

`MakeKeysByType` selects all keys of a specific type, but removes any optional properties.


We should have a good understanding of how to leverage type level programming in TypeScript now. In case you need a better understanding of the basics, the following posts might be helpful:


* [Notes on TypeScript: Type Level Programming Part 1](https://dev.to/busypeoples/notes-on-typescript-type-level-programming-in-typescript-part-1-i57)

* [Notes on TypeScript: Conditional Types](https://dev.to/busypeoples/notes-on-typescript-conditional-types-4bh)

* [Notes on TypeScript: Mapped Types and Lookup Types](https://dev.to/busypeoples/notes-on-typescript-mapped-types-and-lookup-types-i36)



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