Contents
- What are utility types?
- Value transformation types
- Object manipulation types
- Union manipulation types
- Function types
- Class / object-oriented types
- String manipulation types
What are utility types?
Utility types are helpers provided automatically by TypeScript to make common typing tasks easier. Since they are standard across all TypeScript codebases, they are sort of like the "standard library of TypeScript."
TypeScript lets you define reusable types via the type
keyword. There is nothing special about most utility types, almost all of the types are reusable types that happen to be automatically included with every TypeScript installation. All of the definitions for each utility type are freely available on GitHub.
There are a few exceptions to the above, where the types cannot be reproduced since they are built into the TypeScript compiler, such as ThisType
.
Value transformation types
Awaited<Type>
The Awaited
type takes a type that is a Promise
and returns the type that the Promise
resolves to, mimicking the behavior of the await
keyword. The Awaited
utility type is defined as:
type Awaited<Type> = Type extends null | undefined
? Type
: Type extends object & { then(onfulfilled: infer OnFulfilled): any }
? OnFulfilled extends (value: infer Value, ...args: any) => any
? Awaited<Value>
: never
: Type;
For example, if you have a Promise
that resolves to a string
, you can use Awaited
to get the type of the resolved value:
type Value = Awaited<Promise<string>>; // Value: string
The Awaited
type will also recursively unwrap nested Promise
s. If a type is not a Promise, it will be left as-is.
type Nested = Awaited<Promise<Promise<string>>>; // X: string
type NonAsync = Awaited<string>; // NonAsync: string
For clarity, compare the behavior of Awaited
with the parallel behavior of await
in JavaScript:
type Value = Awaited<Promise<string>>; // => string
const value: Value = await Promise.resolve("string"); // => "string" (string)
type Nested = Awaited<Promise<Promise<number>>>; // => number
const nested: Nested = await Promise.resolve(Promise.resolve(123)); // => 123 (number)
type NonAsync = Awaited<string>; // => string
const nonAsync: NonAsync = await "test"; // => "test" (string)
Object manipulation types
Partial<Type>
The Partial
type takes an object type and makes all of its properties optional. The Partial
utility type is defined as:
type Partial<Object> = {
[Property in keyof Object]?: Object[Property];
};
For example, it can be used to create a function that takes an object with a subset of the properties of an existing object:
type User = {
name: string;
email: string;
phone: string;
};
const updateUserDetails = (user: User, newDetails: Partial<User>) => ({
...user,
...newDetails,
});
const user = { name: "Ada", email: "[email protected]", phone: "3214567890" };
const updatedUser = updateUserDetails(user, { email: "[email protected]" });
// => { name: "Ada", email: "[email protected]", phone: "3214567890" }
type Preferences = {
theme: "light" | "dark";
language: string;
allowMotion: boolean;
};
const userPreferences: Partial<Preferences> = {
theme: "dark",
};
One important thing to note is that this does not work recursively, so only the first level of an object will be made partial:
type UserWithAddress = {
name: string;
email: string;
phone: string;
address: {
street: string;
city: string;
state: string;
zip: string;
};
};
const addressableUser: Partial<UserWithAddress> = {
// ERROR: Missing the following properties: city, state, zip
address: {
street: "123 Example St",
},
};
In the example above, name
, email
, and phone
are all optional, but all of the properties in address
are still required.
Required<Type>
The Required
utility type accepts an object type and makes all of its properties required. The Required
utility type is defined as:
type Required<Object> = {
[Property in keyof Object]-?: Object[Property];
};
For example, it can be used to create a variant of another type where all properties must be specified:
interface Options {
optionA?: boolean;
optionB?: boolean;
}
const userOptions: Options = { optionA: true };
// After applying defaults, every option should have a value
const options: Required<Options> = {
optionA: true,
optionB: false,
...userOptions,
};
Readonly<Type>
The Readonly
utility type accepts an object type and marks all of its properties as readonly
, so they cannot be changed. The Readonly
utility type is defined as:
type Readonly<Object> = {
readonly [Property in keyof Object]: Object[Property];
};
For example, you can use this to create a parameter type for a function so that the function may not change any properties in the object:
interface Options {
optionA?: boolean;
optionB?: boolean;
}
const userOptions: Options = {
optionA: true,
};
function runApp(options: Readonly<Options>) {
// Try to change options after starting to run the app
options.optionA = false;
// ~~~~~~~ Error: Cannot assign to 'optionA' because it is a read-only property.
}
In addition to objects, the Readonly
type also has special handling in the TypeScript compiler to work with arrays and makes them a ReadonlyArray
type instead. This ensures that elements in the array cannot be added, removed, or updated by removing associated methods like push
, pop
, shift
, sort
, and so on:
// Readonly<Array<T>> === readonly T[]
function processEntries(entries: Readonly<Array<object>>) {
entries.slice(1); // slicing is ok, produces a new array
entries.push({ id: 1 }); // cannot push, will mutate the array
}
Record<Keys, Type>
The Record
utility type accepts two types, Keys
and Type
, and creates an object type where all keys have the type of Keys
and each value has the type of Type
. The Record
utility type is defined as:
type Record<Keys extends keyof any, Type> = {
[Key in Keys]: Type;
};
For example, the type Record<keyof any, unknown>
can represent a generic object where the keys are some type that can used as a key, and the values are unknown (could be a string, object, number, or anything else). The type keyof any
represents any type that can be used as an object key, so in other words it is effectively shorthand for string | number | symbol
.
type GenericObject = Record<keyof any, unknown>;
const a: GenericObject = {
test: "something",
123: "another value",
};
More commonly, you would use Record
to create an object type where the keys are known, and the values are all the same type:
type Options = Record<string, boolean>;
const options: Options = {
optionA: true,
optionB: false,
};
type User = {
id: number;
name: string;
email: string;
};
type UsersById = Record<User["id"], User>;
const users: UsersById = {
1: {
id: 1,
name: "test user",
email: "[email protected]",
},
};
It is also useful for using with union types to create an object type where every type in the union must have an associated value:
type HttpStatusCode = 200 | 404 | 500;
const httpStatusCodes: Record<HttpStatusCode, string> = {
200: "OK",
404: "Not Found",
500: "Internal Server Error",
};
If a new entry is ever added to the union, it must be added to the object, or else an error will occur:
type HttpStatusCode = 200 | 201;
const httpStatusCodes: Record<H> = {
// ~~~~~~~~~~~~~~~ ERROR: Property '201' is missing
// in type '{ 200: string; }' but required in
// type 'Record<HttpStatusCode, string>'.(2741)
200: "OK",
};
Pick<Type, Keys>
The Pick
utility type creates a subset of an object by including specific properties. It accepts an object type and a union of keys, and returns a new object type with only the keys specified from the object type. Pick
is the opposite of the Omit
utility type. The Pick
utility type is defined as:
type Pick<Type, Keys extends keyof Type> = {
[Key in Keys]: Type[Key];
};
It can be thought of as repeated application of the indexing operator to the object type:
type User = {
id: number;
name: string;
email: string;
password: string;
rating: number;
reviews: string[];
};
type WithoutPersonalInfo = Pick<User, "id" | "rating" | "reviews">;
// equivalent to:
type WithoutPersonalInfo2 = {
id: User["id"];
rating: User["rating"];
reviewers: User["reviews"];
};
// Minimum data needed to represent a project
type MinimalProject = Pick<Project, "id" | "name">;
const fullProject: Project = {
id: 123,
name: "Test Project",
description: "This is a test project",
dateCreated: new Date("2022-01-01"),
dateUpdated: new Date("2022-01-06"),
hasIssues: true,
hasWiki: false,
users: [],
};
// Any type created via Pick will be satisfiable by the source type
const fullProject2: MinimalProject = fullProject; // ok
const minimalProject = {
id: 321,
name: "Project lite",
};
Omit<Type, Keys>
The Omit
utility type creates a subset of an object by excluding specific properties. It accepts an object type and a union of keys, and returns a new object type with all the properties of the original object, except the keys specified. Omit
is the opposite of the Pick
utility type. The Omit
utility type is defined as:
type Omit<Type, Keys extends keyof any> = Pick<Type, Exclude<keyof Type, Keys>>;
Note that this definition utilizes Exclude
and Pick
to essentially perform an inverse selection of properties, where we pick all properties, but exclude the ones that are part of the passed in Keys
union.
Omit
is helpful when you want to create a new type from an existing one, and keep most of the properties the same except for a few. For example, if you have a User
type that contains a password
property, you may want to create a UserWithoutPassword
type that is the same as User
, but with the password omitted.
type User = {
id: number;
name: string;
email: string;
password: string;
};
type UserWithoutPassword = Omit<User, "password">;
const user: UserWithoutPassword = {
id: 123,
name: "Test User",
email: "[email protected]",
};
Or, it could be used to remove props from a React component that are handled automatically by a component:
type ButtonAttributes = {
onClick: () => void;
type: "button" | "submit" | "reset";
children: React.ReactNode;
disabled?: boolean;
className?: string;
};
function Button(props: Omit<ButtonAttributes, "type">) {
// The `type` attribute is preconfigured, so we omit it from the
// accepted props and hardcode it to "button" in the component
return <button {...props} type="button" />;
}
Union manipulation types
Exclude<UnionType, ExcludedMembers>
The Exclude
utility type accepts a union type and a union of types to remove from the passed in union type. The returned type is the union type without the specified types. Exclude
is sort of like Array.filter
or Omit
, but for union types. Exclude
is the opposite of the Extract
utility type. The Exclude
utility type is defined as:
type Exclude<Type, ExcludedUnion> = Type extends ExcludedUnion ? never : Type;
Exclude
can be used to a few types from a union type:
type Numbers = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;
type EvenNumbers = Exclude<Numbers, 1 | 3 | 5 | 7 | 9>; // => 2 | 4 | 6 | 8 | 10
type OddNumbers = Exclude<Numbers, EvenNumbers>; // => 1 | 3 | 5 | 7 | 9
Importantly, Exclude
removes any types from the union which are assignable to the union of excluded types, so it can be used to filter categories of types:
type X = string | null | undefined;
type Defined<Value> = Exclude<Value, null | undefined>;
type DefinedX = Defined<X>; // => string
type Values = Exclude<
string | number | (() => void) | ((...args: unknown[]) => boolean),
Function
>;
// => string | number
For more examples, check out my blog post on union types.
Extract<Type, Union>
The Extract
utility type accepts a union type and a union of types to extract from the passed in union type. The returned type is the union of types which are assignable to the union of types passed in. Extract
is sort of like Pick
but for union types instead of object types. Extract
is the opposite of the Exclude
utility type. The Extract
utility type is defined as:
type Extract<Type, Union> = Type extends Union ? Type : never;
Extract
can be used to remove all but a few types from a union type:
type Days =
| "Monday"
| "Tuesday"
| "Wednesday"
| "Thursday"
| "Friday"
| "Saturday"
| "Sunday";
type Weekend = Extract<Days, "Saturday" | "Sunday">; // => 'Saturday' | 'Sunday'
Like Exclude
, Extract
can be used to filter entire categories of types, since it removes any types from the union which are assignable to the union of types to extract:
type Types =
| string
| number
| boolean
| (() => void)
| ((...args: unknown[]) => boolean)
| Map<unknown, unknown>
| Set<unknown>;
type Objects = Extract<Types, object>;
// => Map<unknown, unknown> | Set<unknown> | (() => void) | ((...args: unknown[]) => boolean)
type Functions = Extract<Types, Function>;
// => () => void | ((...args: unknown[]) => boolean)
type Values = Extract<Types, string | number | boolean>;
// => string | number | boolean
type NumberValue = Extract<
string | number | Date | Array<number>,
{ valueOf: () => number }
>;
For more examples, check out my blog post on union types.
NonNullable<Type>
The NonNullable
utility type accepts a union type and returns a new type that is guaranteed not to contain null
or undefined
. The NonNullable
utility type is defined as:
type NonNullable<Type> = Type & {};
This is a shorthand definition that utilizes how the empty object type and intersection operator interact with each other. A more intuitive definition using Exclude
would be:
type NonNullable<Type> = Exclude<Type, null | undefined>;
It can be used to ensure that a type is definitely defined:
type Value = NonNullable<string | number | undefined | null>; // => string | number
type User = {
id: number;
name: string;
phone?: string;
};
type PhoneNumber = NonNullable<User["phone"]>; // => string
Function types
Parameters<Type>
The Parameters
utility type accepts a function and returns the parameter types of that function as an array (tuple). The Parameters
utility type is defined as
type Parameters<Type extends (...args: any) => any> = Type extends (
...args: infer Args
) => any
? Args
: never;
The Parameters
allows us to extract the parameter types out of a function and use it like any other type.
function add(a: number, b: number) {
return a + b;
}
type AddParameters = Parameters<typeof add>; // => [a: number, b: number]
Note that we need to use typeof add
here to refer to the add
function type, rather than the add
function itself.
This is type is especially useful for creating functions that utilize standard library functions, because we can avoid the need to rewrite the type:
function getUtcDate(...args: Parameters<typeof Date["UTC"]>) {
// args has the following type:
// => [year: number,
// monthIndex: number,
// date?: number | undefined,
// hours?: number | undefined,
// minutes?: number | undefined,
// seconds?: number | undefined,
// ms?: number | undefined]
return Date.UTC(...args);
}
// These filters are guaranteed to work with `Array.filter` since
// we use its callback type explicitly
type FilterFunction = Parameters<typeof Array["prototype"]["filter"]>[0];
const booleanFilter: FilterFunction = (value) => !!value;
const positiveFilter: FilterFunction = (value) =>
typeof value === "number" && value > 0;
const array = [0, 1, null, 3, false, -1, ""]
.filter(booleanFilter)
.filter(positiveFilter);
ReturnType<Type>
The ReturnType
utility type takes a function type and takes on the type of whatever that function returns. The definition of ReturnType
is almost exactly the same as Parameters
, but it infers the return type instead of the parameter types:
type ReturnType<Type extends (...args: any) => any> = Type extends (
...args: any
) => infer Return
? Return
: any;
It allows us to get the return type of any function we pass in:
function sayHello(name: string) {
return `Hello ${name}!`;
}
type Greeting = ReturnType<typeof sayHello>; // => string
function add(a: number, b: number) {
return a + b;
}
type Sum = ReturnType<typeof add>; // => number
One of the most useful aspects is that we can re-use the return types of built-in functions:
type Value = string | number
type Entries = ReturnType<typeof Object.entries<Value>>
// => [string, Value][]
const entries: Entries = [
['key', 'value'],
['key2', 123]
]
Class / object-oriented types
ConstructorParameters<Type>
The ConstructorParameters
utility type takes a class type and returns an array of the types of the constructor's parameters. The definition of ConstructorParameters
is almost exactly the same as Parameters
, but it constrains the accepted types to be a constructable/instantiable type:
type ConstructorParameters<Type extends abstract new (...args: any) => any> =
Type extends abstract new (...args: infer Params) => any ? Params : never;
It allows us to get the parameter types of any class we pass in:
type DateParameters = ConstructorParameters<typeof Date>;
// => [value: string | number | Date]
type SetParameters = ConstructorParameters<typeof Set>;
// => [iterable?: Iterable<unknown> | null | undefined]
type RegexParameters = ConstructorParameters<typeof RegExp>;
// => [pattern: string | RegExp, flags?: string | undefined]
type FormatParameters = ConstructorParameters<typeof Intl.DateTimeFormat>;
// => [locales?: string | string[] | undefined,
// options?: Intl.DateTimeFormatOptions | undefined]
It also works with any type that has a new
constructor:
type Vector = { new (x: number, y: number): { x: number; y: number } };
type VectorParameters = ConstructorParameters<Vector>;
// => [x: number, y: number]
InstanceType<Type>
The InstanceType
utility type takes a class type and returns the type that will be instantiated by the constructor. This is similar to the ReturnType
utility type, but only returning the type of the constructor function. In essence, it strips away typeof
in an expression like typeof Instance
and simply returns Instance
. For example:
type DateType = InstanceType<typeof Date>;
// => Date
type SetType = InstanceType<typeof Set>;
// => Set
type ArrayType = InstanceType<typeof Array>;
// => unknown[]
This also works for any type that has a new
constructor:
type Vector = { new (x: number, y: number): { x: number; y: number } };
type VectorType = InstanceType<Vector>;
// => { x: number; y: number }
ThisParameterType<Type>
The ThisParameterType
utility type takes a function type and returns the type of the this
parameter, if there is one. Otherwise, it returns unknown
. It is defined as:
type ThisParameterType<Type> = Type extends (
this: infer This,
...args: never
) => any
? This
: unknown;
It allows you to extract the type of object that a function expects to be bound to. For example:
// Function that acts as if it were a class method
function vectorLength(this: { x: number; y: number }) {
return Math.sqrt(this.x ** 2 + this.y ** 2);
}
type Vector = ThisParameterType<typeof vectorLength>;
// => { x: number; y: number; }
const vector: Vector = { x: 3, y: 4 };
// Bind an object to the function so that it can be called like a method
const length = vectorLength.bind(vector);
console.log(length()); // => 5
And for most functions that do not have a this
parameter, it returns unknown
:
type NoThis = ThisParameterType<(x: number) => number>;
// => unknown
OmitThisParameter<Type>
The OmitThisParameter
utility type takes a function type and returns a new function type with the this
parameter removed. If the passed in function does not have a this
parameter, it is returned unchanged. It is defined as:
type OmitThisParameter<Type> = unknown extends ThisParameterType<Type>
? Type
: Type extends (...args: infer Arguments) => infer ReturnType
? (...args: Arguments) => ReturnType
: Type;
It allows you to remove the this
parameter from a function type, so that it can be used like a callback or an already bound function:
// Function that acts as if it were a class method
function vectorLength(this: { x: number; y: number }) {
return Math.sqrt(this.x ** 2 + this.y ** 2);
}
type BoundLength = OmitThisParameter<typeof vectorLength>;
// => () => number
const length: BoundLength = vectorLength.bind({ x: 3, y: 4 });
console.log(length()); // => 5
And for most functions that do not have a this
parameter, it returns the same type:
type NoThis = OmitThisParameter<(x: number) => number>;
// => (x: number) => number
ThisType<Type>
The ThisType
utility type is a special type that hints what the type of this
in a function type should be. Its definition is empty, because it is treated as a special type by the TypeScript compiler. When the compiler sees this type, it knows to treat this
as whatever the passed in Type
is.
For example, suppose that we have a user type:
type User = {
id: number;
firstName: string;
lastName: string;
};
const user: User = {
id: 123,
firstName: "Test",
lastName: "User",
};
We can create a function that extends this object with more behaviors:
function addUserMethods<Methods extends Record<string, Function>>(
user: User,
// Note: ThisType is used to indicate that the methods should type `this`
// with the type of `User`
methods: Methods & ThisType<User>
) {
// Casting is necessary here to get the correct type, since
// we've arbitrarily modified the object to add more properties
return { ...user, ...methods } as User & Methods;
}
Then, when we go to use this function, we get a much improved developer experience since we do not need to specify any types in our additional methods:
const newUser = addUserMethods(user, {
getFullName() {
// These properties correctly resolve because `this` is a `User`.
// This code would fail to compile without the `ThisType<User>`
return `${this.firstName} ${this.lastName}`;
},
generateEmail() {
return `${this.firstName[0]}${this.lastName}@example.com`;
},
});
console.log(newUser.getFullName()); // => "Test User"
console.log(newUser.generateEmail()); // => "[email protected]"
String manipulation types
Uppercase<StringType>
The Uppercase
utility type accepts a string literal type and returns a new string type where all characters are uppercase. The Uppercase
utility type is an intrinsic compiler function, so unfortunately its definition is not available.
type Upper = Uppercase<"typeScript">; // => "TYPESCRIPT"
Lowercase<StringType>
The Lowercase
utility type accepts a string literal type and returns a new string type where all characters are lowercase. Unfortunately, the definition of the Lowercase
utility type is not available to us, as it is an intrinsic compiler function.
type Lower = Lowercase<"TypeScript">; // => "typescript"
Capitalize<StringType>
The Capitalize
utility type accepts a string literal type and returns a new string type where the first character is uppercase and the remaining characters are lowercase. Unfortunately, we cannot easily view the definition of the Capitalize
utility type, since it is an intrinsic compiler function.
type Capitalized = Capitalize<"typeScript">; // => "TypeScript"
Uncapitalize<StringType>
The Uncapitalize
utility type accepts a string literal type and returns a new string type where the first character is lowercase and the remaining characters are unchanged. It essentially does the opposite of the Capitalize
. Uncapitalize
is an intrinsic utility type, so unfortunately its definition is not readily available.
type Uncapitalized = Uncapitalize<"TypeScript">; // => "typeScript"
If this article helped you or you have feedback on it, please let me know at @cammchenry! Happy coding and good luck!