TypeScript Utility Types Guide

By Cam McHenry on

SummaryUtility types are included with TypeScript to help with common typing tasks. In this article, we will see how utility types can be used to manipulate unions, objects, strings, and other types.


Contents

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 Promises. 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!