2019-07-15 ~ 3 min

List of Built-In Helper Types in TypeScript

TypeScript has a few very useful helper types predefined, which aren't known widely enough. Here's a list of them with examples and explanations how they work for the more complex ones.

These helper types are either conditional or mapped types. To get an understanding how they work in general, check out my other blogpost Mapped Types in TypeScript.

Mapped Types

Partial

type Partial < T > = { [ P in keyof T ] ? : T [ P ] ; } ;

Required

type Required < T > = { [ P in keyof T ] - ? : T [ P ] ; } ;

Readonly

type Readonly < T > = { readonly [ P in keyof T ] : T [ P ] ; } ;

Pick

type Pick < T , K extends keyof T > = { [ P in K ] : T [ P ] ; } ;

Example:

interface I { a : string ; b : string ; c : string ; } type T = Pick < I , 'a' | 'b' > ;

To ensure proper type checking of the provided properties, K extends keyof T . Since keyof T is a union of all property names, 'a' | 'b' | 'c' in the example, anything that extends it can only contain a subset of those.

Record

type Record < K extends keyof any , T > = { [ P in K ] : T ; } ;

Example:

type T = Record < 'a' | 'b' | 1 , string > ;

The Record type is an interesting one. The example above is how it is supposed to be used. Provide a list of property keys and a type and it creates a type with all of these properties set to the given type.

To ensure that only valid keys can be given, K extends keyof any . The result of that is string | number | symbol .

In my opinion it would be clearer to use PropertyKey instead, which is defined to be the same thing, but it may have historical reasons to be defined keyof any .

Usually keyof gives a union of literal string types. The reasoning of the result of keyof any is that any is no concrete type, it can be anything, with any possible property key. They can be strings, numbers and symbols. So keyof any returns a union of all strings, all numbers and all symbols, which is equivalent to string | number | symbol .

Note: I don't know if this is actually the reasoning. It is just the best explanation I could come up with that doesn't make keyof any a special case.

Technically there are no number property keys in JavaScript (they are converted to strings). The TypeScript team chose to include it anyway, to make it easier for developers and keep consistency. You can read more about that decision in issue #21983 on the TypeScript repository.

Conditional Types

Exclude

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

Example:

type T = Exclude < 'a' | 'b' | 'c' , 'c' | 'f' > ;

Exclude makes most sense when applied to union types, because of the way they're applied to them: Distributive, on each type making up the union separately.

In this example each string, a , b , c is checked if it is contained in 'c' | 'f' , and if not, appended to the result.

The obvious use is for property keys, but it can also be used to exclude types extending another from a union.

interface Base { z : string ; } interface E1 extends Base { } interface E2 extends Base { } interface Other { a : string } type T = Exclude < E1 | E2 | Other , Base > ;

You have to watch out though. TypeScript checks the shape, not the actual type. This means if they have the same property, they are the same type, even if the name is different.

interface Base { a : string ; } interface E1 extends Base { } interface E2 extends Base { } interface Other { a : string } type T = Exclude < E1 | E2 | Other , Base > ;

In this example, now that Base has a property a instead of z , Other extends Base is also true and the result is never .

Extract

type Extract < T , U > = T extends U ? T : never ;

Extract is the reverse of Exclude . Which means the same examples apply, just in reverse.

type T = Extract < 'a' | 'b' | 'c' , 'c' | 'f' > ;

interface Base { z : string ; } interface E1 extends Base { } interface E2 extends Base { } interface Other { a : string } type T = Extract < E1 | E2 | Other , Base > ;

Omit

type Omit < T , K extends keyof any > = Pick < T , Exclude < keyof T , K >> ;

Example:

interface I { a : string ; b : string ; c : string ; } type T = Omit < I , 'a' | 'b' > ;

Omit is the reverse of Pick .

Exclude<keyof T, K> results in all properties that are not in K (which is 'a' | 'b' in the example), and Pick creates the type with the remaining properties.

NonNullable

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

Types are often defined as a union with null or undefined to make assigning a value optional.

For example

interface I { } type T = I | undefined ; const x : T = undefined ;

NonNullable essentially makes it required again.

type T2 = NonNullable < T > ;

Note: For this to have any effect, strictNullChecks must be set to true in tsconfig.json . Otherwise null and undefined can be assigned to any type, and I | undefined is essentially the same as I .

Parameters

type Parameters < T extends ( ... args : any ) => any > = T extends ( ... args : infer P ) => any ? P : never ;

Example:

type F = ( p1 : string , p2 : number ) => boolean ; type T = Parameters < F > ;

The definition looks quite complicated, so let's dissect it. On the left side there is

type Parameters < T extends ( ... args : any ) => any >

(...args: any) => any maps to any function. It's pretty much the same as Function . This means the left side can also be written as

type Parameters < T extends Function >

Written like that it's easier to see that it requires T to be a function type.

On the right side it's not possible to do such a simplification, because the type of args has to be inferred.

T extends ( ... args : infer P ) => any ? P : never

The infer keyword can be used in combination with extends and instructs TypeScript to infer the type of some part of the extends condition.

It can only be used with extends because otherwise there is nothing the type is limited to, hence nowhere to infer the type from. For the same reason it's only possible to reference the inferred type in the true branch of the condition.

In the case above ...args is typed with infer P , which means that the arguments type will be inferred into P .

The return type of the function ( (...args: infer P) => any ) can be ignored, so it's typed any .

If the condition that T is a function is true, the result is the inferred arguments type P .

Since T is restricted to be a function, this is always the case, otherwise there'd be a compiler error.

ConstructorParameters

type ConstructorParameters < T extends new ( ... args : any ) => any > = T extends new ( ... args : infer P ) => any ? P : never ;

Example:

type F = new ( p1 : string , p2 : number ) => boolean ; type T = Parameters < F > ;

This is the same as Parameters , just typed for constructor functions with the new keyword added.

ReturnType

type ReturnType < T extends ( ... args : any ) => any > = T extends ( ... args : any ) => infer R ? R : any ;

Example:

type F = ( p1 : string , p2 : number ) => boolean ; type T = ReturnType < F > ;

ReturnType works very similar as Parameters , the only difference is that instead of inferring the parameters type, the return type is inferred ( infer R ) and the result of the extends condition.

InstanceType

type InstanceType < T extends new ( ... args : any ) => any > = T extends new ( ... args : any ) => infer R ? R : any ;

This is the same as ReturnType , just typed for constructor functions with the new keyword added.