typescript

What are the Built-in Utility Types in Typescript?

Last updated on

What are the Built-in Utility Types in Typescript?

Introduction

In TypeScript, built-in utility types are pre-defined type transformations provided by the language to make working with types easier and more expressive. These utility types can be used to manipulate and transform existing types without the need to write custom type declarations. In this post, we will explore some of the most useful TypeScript utility types and how they can be used to improve your code.

  • Partial
  • Record<T, K>
  • Pick<T, K>
  • Omit<T, K>
  • Required
  • Readonly
  • Exclude<T, U>
  • Extract<T, U>
  • NonNullable
  • ReturnType

Partial

Partial<T> is a built-in utility type in TypeScript that constructs a type with all properties of T set to optional. This means that every property of the resulting type can be either present or absent.

Here’s an example of how to use Partial<T>:

interface User {
  id: number
  name: string
  email: string
}
 
function updateUser(user: User, updates: Partial<User>): User {
  return { ...user, ...updates }
}
 
const user: User = { id: 1, name: 'John Doe', email: 'john.doe@example.com' }
const updates = { name: 'Jane Doe' }
 
const updatedUser = updateUser(user, updates)
console.log(updatedUser) // { id: 1, name: "Jane Doe", email: "john.doe@example.com" }

In this example, the updateUser function takes a User object and an object containing partial updates to the user’s properties. The Partial<User> type is used to define the type of the updates parameter, which means that any property of the User type can be present or absent in the updates object. The updateUser function then merges the original user object with the updates object using the spread operator, and returns the result as a new User object.

Record<T, K>

The Record utility in TypeScript is used to create a type with specified keys and value types. It takes two type arguments: the key type and the value type. Here’s an example:

type Person = {
  name: string
  age: number
}
 
const people: Record<string, Person> = {
  john: { name: 'John', age: 30 },
  jane: { name: 'Jane', age: 25 }
}
 
const john = people['john'] // john is of type Person

In this example, we define a Person type with name and age properties. We then create a Record type with string keys and Person values. We use this Record type to define an object people with two entries, john and jane. We can then access the john entry and its properties using bracket notation.

Pick<T, K>

Pick<T, K> is a built-in utility type in TypeScript that constructs a type by picking a set of properties K from T. This means that the resulting type has only the properties of T that are specified in K.

Here’s an example of how to use Pick<T, K>:

interface User {
  id: number
  name: string
  email: string
  age: number
}
 
type UserBasicInfo = Pick<User, 'id' | 'name'>
 
const user: User = {
  id: 1,
  name: 'John Doe',
  email: 'john.doe@example.com',
  age: 30
}
const userBasicInfo: UserBasicInfo = { id: user.id, name: user.name }
 
console.log(userBasicInfo) // { id: 1, name: "John Doe" }

In this example, the UserBasicInfo type is defined as a Pick<User, "id" | "name">, which means that it only includes the id and name properties of the User interface. We then create a user object with all properties of the User interface, and a userBasicInfo object with only the id and name properties of the user object. We can access the id and name properties of the user object using the dot notation, like user.id and user.name.

Omit<T, K>

Omit<T, K> is a built-in utility type in TypeScript that constructs a type by omitting a set of properties K from T. This means that the resulting type has all properties of T except for the ones specified in K.

Here’s an example of how to use Omit<T, K>:

interface User {
  id: number
  name: string
  email: string
  age: number
}
 
type UserWithoutEmail = Omit<User, 'email'>
 
const user: User = {
  id: 1,
  name: 'John Doe',
  email: 'john.doe@example.com',
  age: 30
}
const userWithoutEmail: UserWithoutEmail = {
  id: user.id,
  name: user.name,
  age: user.age
}
 
console.log(userWithoutEmail) // { id: 1, name: "John Doe", age: 30 }

In this example, the UserWithoutEmail type is defined as an Omit<User, "email">, which means that it includes all properties of the User interface except for the email property. We then create a user object with all properties of the User interface, and a userWithoutEmail object with all properties of the user object except for the email property. We can access the id, name, and age properties of the user object using the dot notation, like user.id, user.name, and user.age.

Required

Required<T> is a built-in utility type in TypeScript that constructs a type with all properties of T set to required. This means that every property of the resulting type must be present.

Here’s an example of how to use Required<T>:

interface User {
  id?: number
  name?: string
  email?: string
}
 
function createUser(user: Required<User>): void {
  console.log(user.id, user.name, user.email)
}
 
const user: User = { id: 1, name: 'John Doe' }
createUser(user) // Error: Property 'email' is missing in type '{ id: number; name: string; }' but required in type 'Required<User>'.

In this example, the createUser function takes a User object as a parameter, but the User interface defines all properties as optional. The Required<User> type is used to define the type of the user parameter in the createUser function, which means that every property of the User type must be present in the user object. When we try to call the createUser function with an incomplete User object, TypeScript throws an error because the email property is missing.

Readonly

Readonly<T> is a built-in utility type in TypeScript that constructs a type with all properties of T set to readonly. This means that every property of the resulting type can be present, but cannot be modified.

Here’s an example of how to use Readonly<T>:

interface User {
  readonly id: number
  readonly name: string
  readonly email: string
}
 
function getUserInfo(user: Readonly<User>): void {
  console.log(user.id, user.name, user.email)
}
 
const user: User = { id: 1, name: 'John Doe', email: 'john.doe@example.com' }
getUserInfo(user) // 1, "John Doe", "john.doe@example.com"
 
user.id = 2 // Error: Cannot assign to 'id' because it is a read-only property.

In this example, the getUserInfo function takes a User object as a parameter, but the User interface defines all properties as readonly. The Readonly<User> type is used to define the type of the user parameter in the getUserInfo function, which means that every property of the User type can be present, but cannot be modified. When we try to modify a property of the user object after it has been passed to the getUserInfo function, TypeScript throws an error because the property is readonly.

Exclude<T, U>

Exclude<T, U> is a built-in utility type in TypeScript that constructs a type by excluding all types in U from T. This means that the resulting type has all types of T except for the ones that are assignable to U.

Here’s an example of how to use Exclude<T, U>:

type Fruit = 'apple' | 'banana' | 'orange'
type ExcludedFruit = Exclude<Fruit, 'banana'>
 
const fruits: Fruit[] = ['apple', 'banana', 'orange']
const excludedFruits: ExcludedFruit[] = ['apple', 'orange']
 
console.log(excludedFruits) // ["apple", "orange"]

In this example, the ExcludedFruit type is defined as an Exclude<Fruit, "banana">, which means that it excludes the "banana" type from the Fruit union type. We then create a fruits array with all types of the Fruit union type, and an excludedFruits array with all types of the ExcludedFruit type. We can access the elements of the fruits and excludedFruits arrays using the bracket notation, like fruits[0], fruits[1], fruits[2], excludedFruits[0], and excludedFruits[1].

Extract<T, U>

Extract<T, U> is a built-in utility type in TypeScript that constructs a type by extracting all types in T that are assignable to U. This means that the resulting type has only the types of T that are also assignable to U.

Here’s an example of how to use Extract<T, U>:

type Fruit = 'apple' | 'banana' | 'orange'
type ExtractedFruit = Extract<Fruit, 'banana' | 'orange'>
 
const fruits: Fruit[] = ['apple', 'banana', 'orange']
const extractedFruits: ExtractedFruit[] = ['banana', 'orange']
 
console.log(extractedFruits) // ["banana", "orange"]

In this example, the ExtractedFruit type is defined as an Extract<Fruit, "banana" | "orange">, which means that it extracts the "banana" and "orange" types from the Fruit union type. We then create a fruits array with all types of the Fruit union type, and an extractedFruits array with all types of the ExtractedFruit type. We can access the elements of the fruits and extractedFruits arrays using the bracket notation, like fruits[0], fruits[1], fruits[2], extractedFruits[0], and extractedFruits[1].

NonNullable

NonNullable<T> is a built-in utility type in TypeScript that constructs a type by removing null and undefined from T. This means that the resulting type has all types of T except for null and undefined.

Here’s an example of how to use NonNullable<T>:

type User = {
  id: number
  name: string
  email?: string | null
}
 
type NonNullableUser = {
  id: number
  name: string
  email: string
}
 
function createUser(user: NonNullable<User>): void {
  console.log(user.id, user.name, user.email)
}
 
const user: User = { id: 1, name: 'John Doe', email: null }
createUser(user) // Error: Property 'email' is missing in type '{ id: number; name: string; email: null; }' but required in type 'NonNullable<User>'.

In this example, the NonNullableUser type is defined as a User type with the email property set to a non-nullable type. The createUser function takes a NonNullable<User> object as a parameter, which means that the email property must be present and not null or undefined. When we try to call the createUser function with a User object that has a null value for the email property, TypeScript throws an error because the email property is missing in the NonNullable<User> type.

ReturnType

ReturnType<T> is a built-in utility type in TypeScript that constructs a type by extracting the return type of a function type T. This means that the resulting type is the return type of the function.

Here’s an example of how to use ReturnType<T>:

function add(a: number, b: number): number {
  return a + b
}
 
type AddReturnType = ReturnType<typeof add>
 
const result: AddReturnType = 3
console.log(result) // Error: Type '3' is not assignable to type 'number'.

In this example, the AddReturnType type is defined as the ReturnType<typeof add> type, which extracts the return type of the add function. We then create a result variable with the type of AddReturnType, which means that it can only be assigned the return type of the add function. When we try to assign a number to the result variable, TypeScript throws an error because the number type is not assignable to the AddReturnType type.

There are more…

The above examples are only few built-in utility types, there are many more and keep expanding you can keep exploring them in Typescript Documentation.

Conclusion

By using these built-in utility types, TypeScript users can write code that is shorter and easier to understand. They can also make their code safer by ensuring that the types are correct. This saves time because they don’t have to manually change types themselves. Learning and using these utility types can greatly improve the experience of developing in TypeScript and help keep the codebase strong and error-free.