I've grown pretty fond of Typescript in recent years. In particular I very much like its Turing-complete type system.

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.

Also, check out SimplyTyped or TypeFest. 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.

This post will get updated from time to time.
Last Update: 17/3/2021 11:15 CET

TL;DR: GitHub-Gist
Skip all the explanations and just see types

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, infer its type-argument and recursively 'call' Await again.
If not, just return the input.

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

Bonus: You can also alter this slightly to check if some type is a Promise

export type IsPromise<T> = PromiseLike<infer U> ? true : false;

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

type IsPromiseA = isPromise<ReturnType<typeof asyncFn>>; // = true
type IsPromiseB = isPromise<ReturnType<typeof syncFn>>; // = false


Length Type

Even simpler is the Length type.
You can use this for example to infer the number of parameters to a given function.
There is, however, another clever use to which we will come back later

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



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

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'

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

// 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 }



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

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 use the false-case and just look at a simpler type

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.

interface MyIface {
	keyA: number;
	keyB: string;

type Demo = Unionize<MyIface>; // = { keyA: number } | { keyB: string }


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

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, it's 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.
For the value of the inner dictionary, we check if the current InnerKey is the 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).

All of this would give 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.

We can still use undefined, though.
To mitigate this, we add an intersection to the inner dictionary, with the inner dictionary we've seen in the Unionize type, as that one doesn't allow undefined.

The result is something like this

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 }


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

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.


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.

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!

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

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

introduced in TypeScript 4.1, they really filled a gap for me.
With them, you can do so much fancy and mindblowing stuff!
Someone even wrote a full-blown CSS-Parser ­čĄ»!

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


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.

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

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, however, for some reason, more restricted than tuples, so we need to build a few types to help with that.


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:

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'


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.