TypeScript utility types: when and how to use them
Tags: typescript, programming, automation
Published: 2024-03-01 15:32:31

---
title: TypeScript utility types: when and how to use them
published: true
date: 2023-11-02 23:00:00 UTC
tags: Typescript, Programming, Automation
canonical_url: https://blog.apify.com/typescript-utility-types/
---

 **The** [**Apify SDK supports TypeScript**](https://docs.apify.com/sdk/js/docs/guides/type-script-actor) **by covering public APIs with type declarations. This allows writing code with auto-completion for TypeScript and JavaScript code alike. This article was written to provide you with a deeper knowledge of TypeScript. But if you want to know more about how it compares with JavaScript for web scraping, you might like to read** [**TypeScript vs. JavaScript: which to use for web scraping?**](https://blog.apify.com/typescript-vs-javascript-crawler/)

## **What is TypeScript?**

TypeScript is a superset of JavaScript that provides you with the capabilities of static type-checking, enabling you to catch type-related errors during development. One very useful feature of TypeScript is being able to define and manipulate types effectively to write code that is maintainable and reliable.

Utility types in TypeScript play a significant role in this regard, as they enable you to create new types based on existing ones.

## **What are utility types?**

Utility types are sets of built-in generic types in TypeScript that allow you to create, manipulate, or create new types by applying specific modifications and transformations to existing types.

Implementing utility types in your TypeScript project can make working with types more flexible, expressive, and reusable. Declaring your own custom utility types or using TypeScript's built-in utility types are the two approaches to adding utility types to your code base.

## **TypeScript built-in utility types**

There are different built-in utility types that come along with the TypeScript language to make type transformations without you needing to install a library or create a custom type to use them. Lets look at some of them:

### **1.** `Partial<Type>`

When defined, the partial utility type in TypeScript turns all of a type's properties into optional fields. This allows you to modify the type's fields in part without TypeScript throwing errors.

For example, say you have `User` data in your application and want to update the information of the user. You only want to update specific fields and not the whole data. Using the `Partial` utility type, you can transform the data fields of the `User`object from required fields to optional fields.

```
// Description of the user datainterface User { id: number; firstName: string; lastName: string; email: string; bio: string;};// This is the user data fetchedconst userData: User = { id: 12345, firstName: "Jamin", lastName: "Doe", email: "hellojamin@test.com", bio: "Legendary Gamer and everything in between",};//Function to update the user infoconst updateUserInfo = (userId: number, updatedInfo: Partial<User>) => { // Logic to update the user's info with the provided data // Using the partial type, all fields becomes optional updatedInfo.lastName; //(property) lastName?: string | undefined updatedInfo.email; //(property) email?: string | undefined};// Example usageconst userId = 12345;const updatedInfo: Partial<User> = { firstName: "John", bio: "Web Developer",};updateUserInfo(userId, updatedInfo);
```

In the example above, the `Partial` utility type converts all the `User` object properties to optional fields.

### **2.** `Required<Type>`

The `Required` utility type is the opposite of the `Partial` utility type. It transforms all the fields of your `Type` into required fields. Imagine you have a data type of `UserRegistration` with some optional fields, but youd like to make all the fields required when using the data. You can achieve this using the `Required` utility type.

```
interface UserRegistration { username?: string; password?: string; email: string; fullName?: string;}const registerUser = (userData: Required<UserRegistration>) => { // Logic to register the user with the provided data};// Example usageconst userData: Required<UserRegistration> = { email: "user@example.com",};// throws an error// Type '{ email: string; }' is missing the following properties from type 'Required<UserRegistration>': username, password, fullNameregisterUser(userData);
```

### **3.** `Pick<Type, Keys>`

This utility type enables you to selectively pick properties from a `Type` using the `Keys` properties of the object you want to pick from.

Lets say you have a product catalog in your application and would like to list available products for a catalog without showing all the data properties of the products. You can create a simplified version of the original product object for listing available products.

```
// Let's create a simplified version of the product for the product listingtype SimplifiedProduct = Pick<Product, "id" | "name" | "price" | "category">;const product: Product = { id: "1001", name: "Laptop", description: "High-performance laptop", price: 1200, category: "Electronics", stock: 10 // ...};const simplifiedProduct: SimplifiedProduct = { id: product.id, name: product.name, price: product.price, category: product.category};console.log(simplifiedProduct);// Output: { id: "1001, name: "Laptop", price: 1200, category: "Electronics" }
```

The `Pick` utility type can be very useful when you have a very complex data object and only want to display a fraction of that data object to your users.

### **4.** `Omit<Type, Keys>`

The `Omit<Type, Keys>` type removes specified `Keys` properties from the `Type` and provides you with a `Type` without those properties.

```
// Let's create a simplified version of the product by excluding certain propertiestype SimplifiedProduct = Omit<Product, "description" | "stock">;const product: Product = { id: "1001", name: "Laptop", description: "High-performance laptop", price: 1200, category: "Electronics", stock: 10 // ...};//description and stock are excluded from the product propertiesconst simplifiedProduct: SimplifiedProduct = { id: product.id, name: product.name, price: product.price, category: product.category};console.log(simplifiedProduct);// Output: { id: "1001, name: "Laptop", price: 1200, category: "Electronics" }
```

### **5.** `ReturnType<Type>`

The return type is used to get a functions return type. This enables you to extract and use the type that a function will return when invoked. It works by taking in a function as its parameter and returning the `Type` of the returned value of the function.

Consider the `fetchDataFromApi` example below and how the return type of the function was derived using the `ReturnType` utility type.

```
type ApiResponse = { success: boolean; data: any; // Assuming data can be of any type};type ApiFetchFunction = () => Promise<ApiResponse>;function fetchDataFromApi(endpoint: string): ApiFetchFunction { // Simulating fetching data from the API const fetchFunction: ApiFetchFunction = async () => { const response = await fetch(endpoint); const data = await response.json(); return { success: true, data }; }; return fetchFunction;}// Usageconst fetchProductData = fetchDataFromApi('https://api.example.com/products');// Get the return type from the functiontype ProductDataResponse = ReturnType<typeof fetchProductData>;// Use the function's return typeasync function handleProductData() { const response: ProductDataResponse = await fetchProductData(); console.log('Product data:', response.data);}
```

### **6.** `Awaited<Type>`

The `Awaited` type is used for asynchronous functions and operations to determine the data type that the function or operation would resolve. Consider the product example you used previously. Imagine you need to fetch the product data from an API. The API service returns a `Promise` with the product data. You want to handle this asynchronously and extract the type of the resolved data.

```
// A function that simulates fetching product data from an APIconst fetchProductFromAPI = (): Promise<Product> => { return new Promise(resolve => { // Simulate an asynchronous API call setTimeout(() => { resolve({ id: "1001", name: "Laptop", description: "High-performance laptop", price: 1200, category: "Electronics", stock: 10 }); }, 1000); });};// Use ReturnType to get the return type of the async function and use// Awaited to retrieve the type of the async callconst getProductData = async (): Promise<Awaited<ReturnType<typeof fetchProductFromAPI>>> => { const productData = await fetchProductFromAPI(); return productData;};getProductData().then(data => { console.log("Product data:", data); /* Output: Product data: { id: '1001', name: 'Laptop', description: 'High-performance laptop', price: 1200, category: 'Electronics', stock: 10 } */});
```

It's interesting to note that the `Awaited` utility transforms types in a recursive manner. So, no matter how deeply nested a `Promise` is, it will always resolve its value. For example, the code below would transform the nested async request to a single data type.

```
type Data = Awaited<Promise<Promise<string>>>;//type Data = string
```

### **7.** `Record<Keys, Type>`

The `Record` utility type enables you to construct an object type with `Keys` as its property keys and `Type` as its property values. The `Keys` passed to the `Record` ensure that only those specific keys can be assigned values of `Type`. This is particularly useful when you want to narrow down your records by only accepting specific keys.

Lets use a real-world scenario to understand this better:

You're creating a notification system for an application that notifies your users based on specific actions they take or responses to a request they make.

You can use the `Record` utility like this:

```
type NotificationTypes = 'error' | 'success' | 'warning';type IconTypes = 'errorIcon' | 'successIcon' | 'warningIcon';type IconColors = 'red' | 'green' | 'yellow';const notificationIcons: Record< NotificationTypes, { iconType: IconTypes; iconColor: IconColors }> = { error: { iconType: 'errorIcon', iconColor: 'red' }, success: { iconType: 'successIcon', iconColor: 'green' }, warning: { iconType: 'warningIcon', iconColor: 'yellow' }};console.log(notificationIcons.error)// OUTPUT: { iconType: 'errorIcon', iconColor: 'red' }
```

In the example above, you specified the `Keys` for the object to be a `Union` type of either `error`, `success`, or `warning` and assigned them to a property value of `{ iconType: IconTypes; iconColor: IconColors }`.

With this, youve created a constraint of records where the `notificationIcons` can only be accessed by one of the `NotificationTypes`. Trying to access the `notificationIcons` without a known `NotificationTypes` will throw an error:

```
console.log(notificationIcons.completed)// Property 'completed' does not exist on type 'Record<NotificationTypes, { iconType: IconTypes; iconColor: IconColors; }>'
```

### **8.** `Readonly<Type>`

The `Readonly` type transforms the object properties of a `Type` to 'read-only' so its values cannot be reassigned after initialization.

Example: Suppose you have a configuration object for a web application with various settings, and you want to ensure that once the configuration is set, it cannot be modified.

```
interface AppConfig { apiUrl: string; maxRequestsPerMinute: number; analyticsEnabled: boolean;}const initialConfig: Readonly<AppConfig> = { apiUrl: '<https://api.example.com>', maxRequestsPerMinute: 1000, analyticsEnabled: true,};// Attempt to modify a property (will result in a TypeScript error)initialConfig.apiUrl = '<https://new-api.example.com>';// Error: Cannot assign to 'apiUrl' because it is a read-only property.function displayConfig(config: Readonly<AppConfig>) { console.log('API URL:', config.apiUrl); console.log('Max Requests Per Minute:', config.maxRequestsPerMinute); console.log('Analytics Enabled:', config.analyticsEnabled);}displayConfig(initialConfig);
```

### **9.** `NonNullable<Type>`

The `NonNullable` type transforms a type by removing all `null` and `undefined` from the input `Type` passed to it.

Example: Let's say you have a union type that accepts a `string`, or a `number`, as values. The type can sometimes be (`undefined` or `null`) as optional values. In the case where you dont want to accept `null` or `undefined` when reusing this type, you can stripe off the `null` and `undefined` fields using `NonNullable`.

```
type UserID = string | number | null | undefined//Works fine as expectedconst userByIDString = "100"// error: Type 'undefined' is not assignable to type 'NonNullable<UserID>'const userByID: NonNullable<UserID> = undefined//Works fine as expectedconst userByIDNumber: NonNullable<UserID> = 102
```

For a list of all the available built-in utility types, check out the official TypeScript [documentation.](https://www.typescriptlang.org/docs/handbook/utility-types.html)

## **Custom types in TypeScript**

Aside from using the built-in utility type from TypeScript, the language also offers the flexibility to create custom utility types to suit your needs where needed. Lets create a custom type in TypeScript that you can use to transform other types. For this example, youll create a utility type that accepts `Type` as an object type and filters the keys of the object by the `Key`passed to the utility type.

```
// Custom utility type declaration type FilterKeysByType<Type, KeyType> = { [key in keyof Type as Type[key] extends KeyType ? key: never]: Type[key];}// Usage exampleinterface Person { name: string; age: number; email: string; isAdmin: boolean;}type StringKeys = FilterKeysByType<Person, string>;//OUTPUT: { name: string, email: string }
```

Lets break down the custom utility line by line to understand it better,

- `type FilterKeysByType<Type, KeyType>` is the type definition, and it accepts two things: the `Type` you want to transform and the `KeyType` to filter by.

- In the second line, three major things are happening:

## **Custom types vs. utility types**

Should you create custom types over TypeScript's built-in utility types?

That depends on the level of transformation and abstraction you're performing. If you want to perform complex transformations that go beyond what TypeScript's built-in features offer, you should consider creating your own custom types.

Utility types are built-in features of TypeScript, and they don't require any external libraries or more lines of code for you to define and use them. Since they're built-in features of TypeScript, they're well-known and familiar to most developers.

## **Combining custom types with utility types**

Utilizing TypeScripts capabilities to create custom types lets you combine a custom type with a utility type to create a customized utility type that is tailored to fit the needs of your project.

Lets explore this with an example. Say you want to create a custom type that transforms another type to make specific properties of that type optional. You can create such a custom type by utilizing TypeScripts already existing utility types to achieve this:

```
type PartialBy<Type, Key extends keyof Type> = Omit<Type, Key> & Partial<Pick<Type, Key>>;// Usageinterface User {id: string;name: string;email: string;}const partialUser: PartialBy<User, 'email'> = {id: '123',name: 'John Doe',};// email is optional in partialUserconsole.log(partialUser);
```

In this example, `PartialBy` is a utility type that takes two parameters: `Type`, which is the original type, and `Key`, which represents the keys that you want to make partial. It uses `Omit` to remove the specified keys from the original type and `Partial<Pick>` to make those keys optional.

With this, the `PartialBy` custom utility can transform the `Key` of any `Type` passed to it.

## **Summing up: why you should use utility types**

Utility types in TypeScript can help you write sturdy, maintainable types. You can use them to make your type declarations more flexible, expressive, and reusable. They also give you the ability to combine them with custom types to create more powerful type declarations.