Advanced Typescript Types, explanations and Cheatsheet
Tags: typescript, cheatsheet, advancedtypes, intermediate
Published: 2022-01-12 19:23:12

---
title: Advanced Typescript Types, explanations and Cheatsheet
published: true
description: Typescript provides a fair share of built-in Utility-Types, however, I always come back copy-pasta-ing from my old code when needing more complex util-types. So here's a cheat-sheet, with examples and explanations, for some of the advanced types I've built and used within the last years.
tags: typescript, cheatsheet, advancedtypes, intermediate
cover_image: https://behind.flatspot.pictures/content/images/size/w2000/2021/03/TypeScriptImage.jpeg

---

Disclaimer: This was originally posted on [my own blog](https://1-3-3-7.dev/blog/advanced-typescript-cheatsheet-pt-1/)

--- 

This post might get updated from time to time.

**TL;DR**: [GitHub-Gist](https://gist.github.com/Alia5/bc7f1dd772c76736c990ed6993996f22)
Skip all the explanations and just see types

---

I've grown pretty fond of Typescript in recent years.
In particular I very much like its [Turing-complete type system](https://github.com/microsoft/TypeScript/issues/14833).

Typescript provides a fair share of [built-in Utility-Types](https://www.typescriptlang.org/docs/handbook/utility-types.html), however, I always come back copy-pasta-ing from my old code when requiring more complex util-types.  
So here's kind of a cheat-sheet, **with examples and explanations**, for some of the advanced types I've built and used within the last years.  

Also, check out [SimplyTyped](https://github.com/andnp/SimplyTyped) or [TypeFest](https://github.com/sindresorhus/type-fest). They contain some truly mind-boggling, but extremely useful stuff.  
I try not to include it in every one of my projects, so there might be some duplicates.

As I find it pretty hard to explain more in-depth types, let me know where you would like to see clarification.
Also let me know if you know any other cool, usefull or mindbending stuff!

So, without further ado, _Let's dive in_

### Await Type

This one is pretty straightforward.
We use the built-in `PromiseLike` type to check if the input really is a promise.
If it is, use the `infer` keyword to deduce its type-argument and recursively 'call' _Await_ again.
If not, just return the input.

```typescript
type Await<T> = T extends PromiseLike<infer U> ? Await<U> : T;

const asyncFn = async (): Promise<number> => new Promise((res) => res(1337));

type ResolvedReturnType = Await<ReturnType<typeof asyncFn>>; // = number
```

[Playground](https://www.typescriptlang.org/play?allowUnreachableCode=true&allowUnusedLabels=true&removeComments=true&checkJs=true&allowJs=true&target=7#code/C4TwDgpgBAgg7gQwJbADwBUB8UC8V1QQAewEAdgCYDOUACgE4D2AtklRADJIDWEqSZAGYR6UAKrYA-LEQpUEqAC58AbgCwAKE2aAxozJVgUBFRBkdAMTK5jp81AAUASmUMWbPmQCuzAEYjsHGwyCDg6JlZ2Bwd6CConXGxYqgcARgBmdIB2Jyd1LQ1QSCgAJTjGABsANwgKMuAvejJ0cGg8eGQ0esbm1tQiiEZBWzNLMkxMFSgAemmbbz8RbQ1iMEZ6IwGoNjdIviwbAmJSShpdjy5eVAQyECkoYHovaGVBBAr2fN19Qyg7MZszmUC38oiCUAy2S+hVaUAAklRzuwYDYdhEPKhuk0WpB+q0hiNzFYJlNZjZHs9NFsEUiIAAhVGI9HsTEQBrYvoDAn-YmTGZzPBvD4QIA)

### Length Type

Another simple one is the Length type.  
You can use this for example to infer the number of parameters to a given function or (readonly) array-lengths
There is, however, another clever use, to which we will come back later.

```typescript
export type Length<T> = T extends { length: infer L } ? L : never;

type MyFnType = (a: number, b: string, c: number) => unknown;

type NumParams = Length<Parameters<MyFnType>>; // = 3
```

[Playground](https://www.typescriptlang.org/play?#code/KYDwDg9gTgLgBDAnmYcAywB2BzGALAHgBUA+OAXjiLlBiwBMBnOAbzgBstc8AuOAS0wAzYFHRwAvnAD84vpmAA3UQG4AsAChNSFHACyiAGKYiyVJQAUAQ3kBXALYAjUQBo4jvoxhRB2NwGM7J1EASgoyW0wAa0wIAHdMdS0NHVQAOQcABSsoK3tmSgwcfAJs3PtgOihGAgNjUxQSEhU4AHpWijgAZiA)

### KeysOfType

I find this one particularly useful from time to time.
It returns the keys of a given interface `O`, but only if they are of type `T`

```typescript
export type KeysOfType<O, T> = {
    [K in keyof O]: O[K] extends T ? K : never;
}[keyof O];


// example 

interface MyInterface {
    keyA: number,
    keyB: string,
    keyC: Record<string, MyInterface>,
    keyD: 1337
};

type KeysOfTypeNumber = KeysOfType<MyInterface, number>; // 'keyA' | 'keyD'
```

This one is not as easy to unserstand, so how does it work?
First, we create a new type that contains all of the keys of `O`,
then we get the valueType of `O` by its key `O[K]` and then check if it is of type `T`
If so, we just use the key `K` as our value, otherwise, we use `never`.
This would result in something like `{ keyA: 'keyA'; keyB: never }`
The last step now is to look up this intermediate type, by all the keys of our interface `O`.
The `never` type gets discarded when looking up a type like this.
But because of this, if we wanted to filter our interface, we cannot just leave out the lookup.

**Bonus**: PickByType

```typescript
// ConvertLiterals would convert literal types like `1337` to their base type like `number` if set to true 
export type PickByType<O, T, ConvertLiterals extends boolean = false> = {
    [K in KeysOfType<O, T>]: ConvertLiterals extends true ? T : O[K]
};

type PickedByNumber = PickByType<MyInterface, number>; // { keyA: number; keyD: 1337 }
```

[Playground](https://www.typescriptlang.org/play?#code/KYDwDg9gTgLgBDAnmYcDSxEGcDyAzAFWWAB4cAaOAgPjgF44BvAWACg4O4BtNOASwB2cANaYIeODgC6ALkk8pcUDGACAJlipwA-OjhyBwAG7AoAbjYBfLqMTjJUi6zZsA9K6UgAhgFswAG1QXVkEVKDwvAGNUAFlEAEkBMIjopjZOEUwAQQMAVx8AI1NydM5bACE5LBgoQQBzEvYyzABhOQAlYEjoNRJq2oEGuDjE5KjgakaM2wAROQBGAGZFgHYrJzYkFHRMXEJiADl8oqh6Hex8IhQSEaTTFOBKAWPTajM4dzOAIlssr7gAD5wH6YGZfYKgSCwOBqLr+LxQVBbVBXYAtLzVEgESgAVVoDAInhU6k0OJ0WgMxlMG1Yn3KEGemjcHhaDJMsAAMnwwl5-JoAO4QXL+NRwboCdnwfzc0y8hDETTS0RwAAGS1WKoQEAQAAtgHxTgUMUjiHAlagVc9CqZNXwJFhgPAYNqarkgqxIdAnaaAAp8SLCcqIVFkSjYuCsiWmGBcnl8omqDRwAoQCCBLxCBgRPkTM4sJocHj8IQYC77a4UKjUWQRtnR2Oy+PKROaV2oXSEuQ4BTrYLIuB+gPANRBo7W04MQeB4PEG4JO7hcZPF5QN4fDyMTKIHJwK0nd6zBbLFZwSxAA)

### OneOf

Suppose you have an Interface (or intersecting Interfaces) and you want to create a (strict) Union type from it.
In the aforementioned _SimplyTyped_ library, there exist types with which you can do the same thing.
I personally like this version better, though, as it's a single 'one-liner' (_cough_) type that one can even parameterize.

Let's start with just the type

```typescript
export type OneOf<T, Strict extends boolean = true> = {
    [OuterKey in keyof T]: Strict extends false
        ? { [K in OuterKey]: T[K] }
        : { [InnerKey in OuterKey|keyof T]?: InnerKey extends OuterKey ? T[OuterKey] : never } & { [TheKey in OuterKey]: T[OuterKey] } 
}[keyof T];
```

Now break this down somewhat.
First, we omit the second type parameter `Strict` and only use the then false-case and just look at a simpler `Unionize` type

```typescript
export type Unionize<T> = {
    [OuterKey in keyof T]: { [K in OuterKey]: T[K] }
}[keyof T];
```

This creates a type with the keys of the input type `T` as key and a new dictionary type containing a single `key-of T` and the corresponding `value type of T` as value.
Afterward, we use the look-up trick we already used above to get a union type of the inner dictionary types (hence the unionize name)
```typescript
interface MyIface {
	keyA: number;
	keyB: string;
}

type Demo = Unionize<MyIface>; // = { keyA: number } | { keyB: string }
```
However...
```typescript
const a: Unionize<MyIface> = { keyA: 1337 }; // ok
const b: Unionize<MyIface> = { keyA: 1337, keyB: 'foo' }; // also okay
```

This is because union dictionary types are not exclusive.
In some cases, it can be okay, other times you'd want it to be strict.
So let's look at that
```typescript
export type OneOf<T> = {
    [OuterKey in keyof T]: {
            [InnerKey in OuterKey|keyof T]?: InnerKey extends OuterKey ? T[OuterKey] : never
        } & {
            [TheKey in OuterKey]: T[TheKey] 
        } 
}[keyof T];
```

Now, this is a bit more complex.
If we look closely, however, it is the same as the `Unionize` type shown above **except for** an intersecting type: `{ [InnerKey in OuterKey|keyof T]?: InnerKey extends OuterKey ? T[OuterKey] : never }`.
Let's break this down some more.

`OuterKey` is a single key of our input-type `T`. Create a union with that and with all `keys of T` as keys for the inner dictionary (`[InnerKey in OuterKey|keyof T]: ...`).
For the value of the inner dictionary, we check if the current `InnerKey` is the `OuterKey` (`InnerKey extends OuterKey ...`).
If it is, we use the value-type of our original type `T`, otherwise `never`.
The last part is to make every key for this dictionary optional (the `?` after the key declaration).

Now this gives us a type which, has _all keys of the input-type_, but **only a single key** is allowed to be assigned with a value-type **other than undefined**.
That means we can still use `undefined`, though.
We can still use undefined, though.
To mitigate this, we add an intersection to an inner dictionary, that being the one we've seen in the `Unionize` type, as it doesn't allow undefined.

The result is the something like this

```typescript
export type OneOf<T, Strict extends boolean = true> = {
    [OuterKey in keyof T]: Strict extends false
    ? { [K in OuterKey]: T[K] }
    :   {
            [InnerKey in OuterKey|keyof T]?: InnerKey extends OuterKey ? T[OuterKey] : never
        } & {
            [TheKey in OuterKey]: T[TheKey] 
        } 
}[keyof T];


// example
interface MyInterface {
    keyA: number,
    keyB: string,
    keyC: Record<string, unknown>,
    keyD: 1337
};

type OnlyOne = OneOf<MyInterface>; // ^= more or less strict version of { keyA: number } | { keyB: string } | { keyC: Record<string, MyInterface> } | { keyD: 1337 }
type NonStrictVersion = OneOf<MyInterface, false>; // { keyA: number } | { keyB: string } | { keyC: Record<string, MyInterface> } | { keyD: 1337 }
```
[Playground](https://www.typescriptlang.org/play?#code/KYDwDg9gTgLgBDAnmYcDyA7YaBmAeAFQBo4BlGKASwGN5QZgMATAZzgCMIIAbYAQwxwAvAigBXYAD5hcAN4BYAFBwVcANpoxDKAGlgiOJUEBrfRBxwCAXQBcZCjTogGzNjj7cWqAPxz1Ow0FNbT1EW0s1HSs4AF84O1l1AEkMLF19QPQtYHTEAB9TRHNLK287FLTQuHpGViyQjN8CDWzc6LssADcc2LgAMj81AgALYCqjepzQ8Obgqf1ouKUYtULi6wBuJSUAeh3qkD4AWzBeJSNtd2pUAFlEFMu+a7klVThCgEEOsSP2HKJXqpCgAhOwsBwYADmAOUQP0AGE7AAlYDUaBMPDgqhQkhiDDGDAQADuGEkMLehQAInYAIwAZjpAHZlltFEokCh0BhuIhMKgRHzcHg7g8clcpBs4Hs4AA9ERHaCoaBwXgsNhYxxwbpQFiUCCCYqJT7fX49OJ5PwgsEQyG9C1GhHI1HozE2kgijCPa7Sc2W-TUuD0pmxdnIVAAOX15CotAAajldfqZIL8B6vcASO5PBKpfsHYgvnAMD8-lA7X7EKC4BqoeX84i4Ci0VAMTXoXA02KnlI6+9-bSGYyQ2yR2iMOC4Hw7CnhfdPV3vTIFLCVMbA4OWdtR-qJ8AadOsELO1BxdIRMuKfpC3imMAcEZgExN9vx3QAEwH7Cpufps8vFeTuw1C0jCMSskoY67nSn5Hj+C49uegKrleIFIX2lZ2AA5ISKCYc+EE7vAEDGHwiAAPpGGRhKvjGMAwd+oont2mYeF4f4XnCBaoQBVpwNhEC4fhiiQXQACs9Gzoxp5Lmha40uSnFVm+QkidUABsEnHtJiE8ShgYKchGF8TSb50phBnoQ2iTsNwU5yHEMQWVSA5Ms+QA)

## Tuple Manipulation

Tuples, combined with the ability to spread them and infer single or even rest-types, are an incredibly useful tool when dealing with more complex types.
So here are a few basic manipulation types starting with:

### Push, PushFront, Pop, PopFront, Shift, ShiftRight
```typescript
type Push<T extends unknown[], U> = T extends [...infer R] ? [...T, U] : never;
type PushFront<T extends unknown[], U> = T extends [...infer R] ? [U, ...T] : never;
type Pop<T extends unknown[]> = T extends [...infer R, infer U] ? U : never;
type PopFront<T extends unknown[]> = T extends [infer U, ...infer R] ? U : never;
type Shift<T extends unknown[]> = T extends [infer U, ...infer R] ? R : never;
type ShiftRight<T extends unknown[]> = T extends [...infer R, infer U] ? R : never;
```
These should all be pretty straightforward.
**Note:** You could just as well use any instead of infer X where X isn't used.

### Reverse

Since there are no loop-functions for TypeScripts type-system, and we somehow need to iterate over the input tuple, we have to use recursion.
To check when the recursion needs to end, we can use the aforementioned `Length`-type. Afterward, just Pop from one tuple, and Push to another.

```typescript
type Reverse<T extends unknown[], U extends unknown[] = []> = Length<T> extends 1 ? Push<U, Pop<T>> : Reverse<ShiftRight<T>, Push<U, Pop<T>>>;
```

A little bit special here is the second type-parameter, which is default initialized to an empty tuple.
We basically use that as our return value, after recursively filling it from the input `T`.
If you wanted to restrict any other devs in your codebase to do nasty things with the second type parameter, you can just wrap the above implementation.

### Filter, TupleIncludes

Next, let's look at a filter-type. This one's also pretty simple.
First, check if our tuple is empty. If it's not, infer the first Element `F` and the rest of the tuple as another type `R`.
Then we check if `F` is equal to, or extends, the type we want to filter out.
If it is, return _Filter_ again, but this time on the rest `R` without `F`.
Otherwise, we return a new tuple, with `F` at the front and a spread (...) of the filtered-rest.

Combining the `Filter` type with our handy-dandy `Length`-type, we can also build an Includes-type by simply comparing the lengths of the filtered and unfiltered tuple!

```typescript
type Filter<T extends unknown[], U> = T extends [] ? [] : T extends [infer F, ...infer R] ? F extends U ? Filter<R, U> : [F, ...Filter<R, U>] : never
type TupleIncludes<T extends unknown[], U> = Length<Filter<T, U>> extends Length<T> ? false : true
```

[Playground](https://www.typescriptlang.org/play?#code/PTAyFMDsHMBcAsAEsCeAHciDumCGAbAJ3FwBMVEBrSAeywChwAPNGw2ZdTCGBAHgAqAPkQBeRAMTNYUUgGdEAb0T4oceAC5EAS0gAzcIUShEAX0QB+Y4i2RwAN0MBuevRCIARrjnaAxogBXWG18OXpUDEQABQC5eEEpJhlIeUDIajpIAG0AXQAaRABVEXFJaVkFLIA6Gt0DIwAlHMtEapqBAsLm2wdncK5o2PgAMUIaSFgE8pSFAPTaLGz8opKJROTUtqq6w0QmlqzCgpqqgW7EO0dCFwjMKJo0KaSKtIzF3NWy55nWk53Ggr-IrNKyFGwXXrXfqRe5oUbjSZfDazeaZD5iNbTTZAo6IP76Xb7UHgy59W6IADK8G0ekR6xeczeS0+9J+WRxx1qBMaIL2JMhNwGVJpsAa2mg8DpWJRTPRpVZm3x9T2gO5wJaDX5VxcbmAiAAtmw8KR7LhIL5wKRAsFQtDMA1IXJwE9ka8FktOgqZe7chi5cY1PxhF7EABGFoxOJ8XGwwRCERaB1XJ18YW0sUSxFCAqR+Ixh5x+OucnDEIyQguhmo97LYoYpEvX1WX1aBtsoHDTnbNVExDDENgqyl-DlvgNToJ1qdvE1Yej8crc6kwh2iQBNCqACS5vwAVI4DklZ+jJ9tdWPHUfDnhkEE5E0oDvHiwasegITvBsEIAXArncABEAFEAFkAHlV3-cBDVzDFcz4LIAHJcAQ5ZEI8FChCcRB3HERDkIKBD0JyCCoJoXN4QmWChgoyY8JQgo0IwrCcNaQiEII5DiPJSDoIeWCCzogiiMw7C9XENiSN4uExko8RYRo+CkPYxA2JyESWKUySaDTDhxB0xT8JU4TmLE1iiK0nSM0lDFLPFSUDOU1T1NMujiK0pNDA-cQPMIFNBKMxzfCY0S-QQoKhMczjiwGHiaGvIxxHihyIoIoLljY5zQsMsKUK07dfF3fc5AABnrdctx3PcD2SgLUvogKRBYr8fzyyqivDUpyvAfLCuq-y2Lq9LSAQxrTLfUJf3oIA)

You may have noticed that I didn't actually call the _Includes_-type like that, but rather _TupleIncludes_.
We'll come back to that just below...

## Template Literal Types

My favorite "subtype" of Types.
Introduced in [TypeScript 4.1](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-1.html), they really filled a gap for me.
With them, you can do [so much fancy and mindblowing stuff](https://github.com/ghoullier/awesome-template-literal-types)
Someone even wrote [a full-blown CSS-Parser](https://twitter.com/anuraghazru/status/1310634306092462080) 🤯!

Fancyness aside, I also want to share a few types I've found useful to me.

### StringIncludes

The nice part about template literals is that you can infer literal types from them.
Even nicer: You can infer empty strings!
Which makes a type that can check if any string includes another pretty trivial.

```typescript
type StringIncludes<S extends string, D extends string> = S extends `${infer T}${D}${infer U}` ? true : false;

type Demo1 = StringIncludes<'a.b', '.'>; // = true
type Demo2 = StringIncludes<'ab', '.'>; // = false
type Demo3 = StringIncludes<'a.', '.'>; // = true
type Demo4 = StringIncludes<'.', '.'>; // = true
```

As we now also have a `StringIncludes` type, we can now combine that with the `TupleIncludes` from above, to get a _completely generic_ `Includes` type
```typescript
type Includes<T extends unknown[]|string, U> = T extends unknown[] ? TupleIncludes<T, U> : T extends string ? U extends string ? StringIncludes<T, U> : never : never;
```

Template literal types are (or were, at the time of initially writing most of this), however, for some reason, more restricted than tuples, so we need to build a few types to help with that.

### Split 
```typescript
type Split<S extends string, D extends string> =
    string extends S ? string[] :
        S extends '' ? [] :
            S extends `${infer T}${D}${infer U}` ?  [T, ...Split<U, D>] : [S];
```

You may wonder why the first conditional is `string extends S`.
If you haven't guessed, it's the check if `string` itself is passed as a type argument, instead of any literal.
Any literal string extends string - but string doesn't extend any literal string

Here's some code to better explain:

```typescript
type Demo<S extends string> = string extends S ? 'used string' : `used literal: ${S}`;

type D1 = Demo<'str'>; // = 'used literal: str'
type D2 = Demo<string>; // = 'used string'
```

### Join 
```typescript
type Join<T extends unknown[], D extends string> = string[] extends T ? string : T extends string[]
  ? PopFront<T> extends string ? Length<T> extends 1 ? `${PopFront<T>}` : `${PopFront<T>}${D}${Join<Shift<T>, D>}` : never
  : never;
```

Using what we've learned so far and some of the tuple manipulation types above, we can now join any string tuple back to a string.

[Playground](https://www.typescriptlang.org/play?#code/PTAEFcBcEsBsGcBQkCeAHApqAMhgdgOaQAWAPACoB8oAvKOaBgB6T4Am8oA3qLPkcQBcoaHgBmGAE45QAX1AB+GcLwYAblIDciZOiwAFAPZoAYpMN5IFRi3adweANZ5DAdzwBtALrU6DZqx4HKAeohLSAKoANKAAdPFhUqAASl6KoBGgKupaupigAMrE0GJW-rZB9k4u7t6+9DaBwaHiSdFxCa3SqenJWaCqGpLaiCCgqJhIE1gFaLDQVgWNdqDwkJKiBDEAIsuVq+ubvoigpwcbhHvBS0prFwTeWSdnL0sBKwDkH+mPgs8vAMKV04AAMACRcRLSciyCHbWGQroZWQg9IhcgxeKxWbzKztbaUNLCDwFLwjaagABShlE1ne+wczjcni8O2B5yOtA5hEe9OCDFuh0uwnKTU4d023meSiMpnMlgo1D54qFBHSuEIJEV7IAjOlwVxZWYLGVKCj+gajfLTQj4RDqbSiiVTTszajskNnh7cqMwNsMABbQw6Cn+oM4hYABi5EasEsIMQ+sQ+lE0oDGdHjDzJeSwYcMsb1dFjpA+AENYgAjD6J5Op9NgOgecs10Afas50OBgtzBYAJhjvas5ertZTaYzIRHHxzudA+YdeGjdEXpHzscjic044bXKzc4XNLwRapR7X3cLW53k4ARGXNJWbwfu4uByuz+uh32r-Xb2XHzoiBAA)

## Example using all of this

A cool and really useful example, using most of the above util-types, is dealing with "Paths" and nested-types of dictionaries.

(Explanations for those are left as an excersice for the reader)

Let's start with valid Paths

### ValidPaths, ValidPathTuples

At the time of initially writing this, the Tuple version was the only one working.In most recent TS versions both work, however, the string literal version has got some gotchas and so the tuple version is somewhat preferred.

```typescript
const dictionary = {
  someProp: 123,
  nested: {
    moreProps: 333,
    deeper: {
      evenDeeper: {
        deepest: 'string'
      }
    },
    alsoDeeper: {
      randomProp: {
        anotherProp: 'wtf'
      }
    }
  }
} as const;

type MyDict = typeof dictionary;

// If you look at type "Debug" below, this surprisingly works.
// You may get a `Type instantiation is excessively deep and possibly infinite.`-error if you're using an older TS version, though
type ValidPaths<T> = keyof T extends never ? never : ({
     [K in keyof T]: T[K] extends never ? never : T[K] extends Record<string|number|symbol, unknown>
       ? K extends string ? `${K}.${ValidPaths<T[K]>}` | K : never
       : K
   })[keyof T] & string;

type Debug = ValidPaths<MyDict>;

// With Tuples.
type ValidPathTuples<T> = keyof T extends never ? never : ({
     [K in keyof T]: T[K] extends never ? never : T[K] extends Record<string|number|symbol, unknown>
       ? [K, ...ValidPathTuples<T[K]>] | [K]
       : [K]
   })[keyof T];

type DebugTuples = ValidPathTuples<MyDict>;

// on recent TS versions you can even join the tuples back to "PathStrings"
type DebugTuples1 = Join<ValidPathTuples<MyDict>, '.'>;

```typescript
[Playground](https://www.typescriptlang.org/play?#code/MYewdgzgLgBAJgS2FB4CGAnAnjAvDAbwCgYYIQBbAUwAUMQAHALhgEYAmAZgBoSYwq0KnBbFSpCiAy16DCC06Le4+FSoMqGUXxVUAblTAARNRq2EdK1esFQWAcmgYEYAOb3LpAL6Wvy8WgANuQmNuZiVhhoYHCUdIzaVgFgIFAAFprxzDD2AO5QAGYeST4qpTA+XjBoEDCgkFAA3EREUFgaMACyWEZIsPhtGiAF8H2oYJhYzURUAB4MUrCDVDAAMoau6QA8ACoAfHgwOzBzUIZwtQQwgRvpLC4FmmsVMAD8zywCBhjNyzAAUiAXLsTrMzjFagBXMAAaxSuTAAG0ALrcGBGUHgi5kKDONwHfDHU7nWpOFyuFFvGA0RgAMXoYCguwOxIhOLxrip6zc232mJJbCpAAMACQEGkMengJn7LxCmAsUXiukMmV7LxiowagiA4EAZTSCAKarRRnV8s++ielu+jRgrXaKwlUsZINZ2OhcJACJRBKO-LZiIeTwAqmiAHSR4MYGAAJWRVJDCv4Vp+Do6BqNMoDHth8KRyL9RLBAqDYEeMbDMEj4ejcYT71jya+mmmAHo2zAAJIjLAgSHXEAgGHVJaOmAAIhMACNIa4JzBp1RAt60ekEKTIRgGM4IOTAjhclIYRBw0QOzAAJr9mAUNA4VxUWBoGBCnbjlzQaIoNAocAwDdQWAQQ9wMA9rAYaoYhgBYID3adwIeFwEDOcMhQAWk0egYyNGA+0hexpBgSE9zcKCYBAQI4CeHY9Rgb493ANc0n7Vw0nTFYADUggQOAaF-NIIGZQ4YSoPsRmLLFahbGN3hk5MAAoInEREAGkALAGBRPEo5kRYHY1ITd1pNTKl5P0wyc1qWMqFADA4C2Mk3AAHzASEKCXDBnIgLAPMotFPXzPZPFId51OM9lyWFMVVK8cMxW4wJeP49IhIM1TCzlGBnJgdSbU0ELk1UnQvAASkRbThl0mAADJIrcaY-hnOdDkS5KBKE7pemQPZ207AB1FC0iOSEGBuU8OJgNq+IEnZRvG4T8EqiSrJTb4zNMlglM8NSNK0sSqp2PSjksiL5LkzaToy1abLshynNcVz3M87zfOnfziLzb0wGCpIqTUiNI2mlK0jmsbBF2Qy9gTHLDMKlg4fEMqKoOiTkUa8dmtcMHxtaniZvSHGIa6vpepaC9-2kYDGSOOiGPGWp8LqaITgMTSACsgU09IVigebBEXNBgBHKAQEnEG9VxckIAnSasaJiBWEOXUwC2YHZv5zqelJtF7HDexeqAA)

### NestedType, NestedTypeByTuple

We can also craft a type that gets us the nested-type of a dictionary for any valid path, or throw an error if an invalid path is passed.
And of course we can put it all together in a beautiful function where only valid Paths are allowed as parameter and the return type is automagically deduced for us.

```typescript
// string version
type NestedType<T, P extends string> = (
  Includes<P, '.'> extends true
    ? PopFront<Split<P, '.'>> extends keyof T
      ? NestedType<T[PopFront<Split<P, '.'>>], Join<Shift<Split<P, '.'>>, '.'>>
      : never
    : P extends keyof T ? T[P] : never
);

type DemoNested1 = NestedType<MyDict, "nested.moreProps">; // 333
type DemoNested2 = NestedType<MyDict, "nested.deeper.evenDeeper">; // { readonly deepest: "string" }
type DemoNestedNever = NestedType<MyDict, "nested.randomProp">; // never

// tuple version
type NestedTypeByTuple<T, P extends string[]> = (
  Length<P> extends 1
    ? Pop<P> extends keyof T ? T[Pop<P>] : never
    : PopFront<P> extends keyof T ? Shift<P> extends string[] 
      ? NestedTypeByTuple<T[PopFront<P>], Shift<P>>
      : never : never
);

type DemoNestedTuple1 = NestedTypeByTuple<MyDict, ["nested" ,"moreProps"]>; // 333
type DemoNestedTuple2 = NestedTypeByTuple<MyDict, ["nested" , "deeper", "evenDeeper"]>; // { readonly deepest: "string" }
type DemoNestedTupleNever = NestedTypeByTuple<MyDict, ["nested", "sillyProp"]>; // never

// String version internally using tuples
// Bonus: Also errors now
type NestedTypeUsingTuplesAgain<T, P extends ValidPaths<T>> = NestedTypeByTuple<T, Split<P, '.'>>;

type Convoluted = NestedTypeUsingTuplesAgain<MyDict, 'nested.alsoDeeper'>;
type ConvolutedError = NestedTypeUsingTuplesAgain<MyDict, 'nested.nonExistant'>;

// And now we can finally give lodash's `get` function a run for it's money
function GetByPath<T, P extends ValidPaths<T>>(dict: T, path: P): NestedType<T, P> {
 // internal impl. must be different. only demoing types here!
 return (dict as any)[path];
}
```

[Playground](https://www.typescriptlang.org/play?#code/MYewdgzgLgBAJgS2FB4CGAnAnjAvDAbwCgYYIQBbAUwAUMQAHALhgEYAmAZgBoSYwq0KnBbFSpCiAy16DCC06Le4+FSoMqGUXxVUAblTAARNRq2EdK1esFQWAcmgYEYAOb3LpAL6Wvy8WgANuQmNuZiVhhoYHCUdIzaVgFgIFAAFprxzDD2AO5QAGYeST4qpTA+XjBoEDCgkFAA3EREUFgaMACyWEZIsPhtGiAF8H2oYJhYzURUAB4MUrCDVDAAMoau6QA8ACoAfHgwOzBzUIZwtQQwgRvpLC4FmmsVMAD8zywCBhjNyzAAUiAXLsTrMzjFagBXMAAaxSuTAAG0ALrcGBGUHgi5kKDONwHfDHU7nWpOFyuFFvGA0RgAMXoYCguwOxIhOLxrip6zc232mJJbCpAAMACQEGkMengJn7LxCmAsUXiukMmV7LxiowagiA4EAZTSCAKarRRnV8s++ielu+jRgrXaKwlINZ2OhcJACJRBKO-LZiIAdEGHk8AEpokMYGAAVWRVOjCv4Vp+Do6EqljJdYIF7vhSORPqJ2f9kZjaKDAdLobj7wTNs0v0dMANRplfrdsLz3sORaxtURpej5eDYEeUerVNDia+DZaf1pCECZwwWb7MFznvzaOjhfb-ZrMEpLF7AoHo6etOHlfP44PtL3MapC6Xmi24aOBxYiMvMArz+Xb7bnscb1hgqYrDskIMDcACSYDAIEkJwIIq45p2m4okBhzcpsaRbP+r47EBLLFtiOG8gc7wFEEEArCwuKQlQ4HNri5JwQhSEoXqD5km4po8ax+KHNxrq1EqpY7NqWpioOcpUgxdEwNRwRUHazHsYhyEQKhbIbl6yIAD68a4WGEg+en5lSkHQVQGmcdpRExp+vqiey5LxgJHJUnqgmuHZWm7FhoHTsmLRzAsGBLE2erQQgTIiaRpK+fxrnGQSljGQ+3HvMZR6eOICVrvY9hUnlSRJIVAriTeRxSdqsnyu8h6ORWMWBHFWxDuiwGJoierItMzEAGpBAgcA0Gg6QOT6MJUFgwwuYlSbfFSM5RiwAAUETiIiADSMAuDAs3zSMOzIsee1xq5a2rcmiY7JdD6hlQoAYHAWzGQZYCQhQABGmhGVgf0gIEaIWXs+VUvtqW+cKYq7V4AZiiN7XjZNaQOZd5owAZMD7aBkMsLtOheAAlIix0LWdMAAGRuW40x-CjY0Tek1k3NNhyU6dD43e8N2bdtpB7QdYBHXNVPnUcj3XXd-N3Rdu1XUtz2ve9n3fX9AMQEDv0g2D6EIhD5XvHtV7M2jbNQRzuxY3GuOXYTh5KyT5Pc0cA0tEQAD03v05y3wQOMzEAHK2MIOyOoF1KeeSPobXw-koTQaL2AG9gkWuCmWO86aqlsbUdSnOTp3smcCu7Oz5e8YdCHAkcaLbefSgXsVMsXacZ8BaK6mABeGsarfte3qel3so9d-lBMqCwNAPpXVmIjQIHLZoRCk4zTYmJItdnHArCHLvEdR90vTIGiABEAh1wGkjSFkEAX3sdq+zAiicMx28gEfcDsIf4f1xPj0Pol9r57wDMhMIAYrTGFMJoJ+L8-ZXGkGgWIYBAg4EgRoaALAL7GQvhUT+VAd4ALDitfAP8G5UC2KfEBMAr4AIDFEGIcRZAIJgK-NaLRX5QGtisQOwc-iUMdAAISwOzahjk54ww5N2fACdSDkTwjQcubJWA52pIwLYKj54Sx5u8B6zoVEry4TPTRkp846Ncgvd4LZB5WKWrlOM1cYDCI0GIiRTcVQt2MWiOx7cy5T1XutYJ69N4dC-pQvhB8KEAKoR4vhNDgHn0PAwuuBDuAXzvjIRgj8CyILfooIhJC64SL-rE0pojxGJNoSkxEaS94ZPoVg+Bl8YGhDMBffJHCkEwBQWgjB1hsF2HofgwhfxIlxL4WQp4FS97xOqTZJJZ8oBonqWA4QF9L5B0CBgrIXTn49NCT7P2PkvICPAKLZcExdk4EhEHNwMBeE2QgCcmAIjwD3JYAAQWCCAE4GB6AYFqPCUOcTHTRgea4CREBvmuDQMCKRD4LaswxsyH0biqAJKWY5QuI8S5d3CSsAAwuAPQINIR73-pUjQkLyQwrhQivutTVk5A2XAAMNEQAdM0BnRsHRSVgHJYhPeABRQFUhqXzIhVChl8LgQstTuygMKQwCitmAgaA0QoB8u4X7b5MR+CehgLkFYwBohKRcEEQZrgEAGGuCAOANQ0iOBgEKVwVAoDygKNCZA4xqh9OhEpSVcVXWSAEFgIgPr4IoEuQAcU9WI1F0dpFLRRejaaewNqIGQMeNEDB0az1JiwTFKaDhiFfi4a5QQDoUGggGGAFB7mwH+qMAoY5DBQAbeAQZsQKAuDSJyZYtQMjSAAIQkGkLwjAYts19GqLUaIWByYFvSJ7HwRB6jQFUJIVgKNGKHATVAJN6M51+vQNgUBjDskPwvhvYpIBWBUMOMsBayFd37tUkc9+LQt2wHfSAdgn7D2JqwKis9saJiXvocqrlPKMBMOiH2rInKUjpEyGw+9EziGAefQMR0b6cNAaCIxAp+QChez-TuwD4qgUgePWB09ObIOTEvgAK2bQGcg1AuOLgwVx3h7a73TCAA)