12 Must-Have TypeScript Utility Types with Uses and Examples
Tags: typescript, react, javascript, webdev
Published: 2023-08-29 18:12:36

Introduction of TypeScript and its static typing capabilities has had a profound impact on the developer experience in the JavaScript world, making it now the industry-standard for most large-scale codebases. 

However, TypeScript's value does not stop at static typing. It showcases its prowess through a collection of several utility types – predefined constructs that allow developers to seamlessly modify and utilize existing types: from `Partial` for crafting optional properties to `ReturnType` for inferring function returns, streamline complex type manipulations, saving developers both time and mental bandwidth. 

In this article, we'll be talking about 12 **underrated** utility types that you should be using more in your codebase. These TypeScript utility types are like tools in your coding toolbox, each serving a unique purpose to help you shape your code with precision and clarity.

## Table of Contents
### Object Manipulation Types
* Partial<T>
* Required<T>
* Readonly<T>
* Mutable<T>
* Pick<T, Keys>
* Record<Keys, Value>
* Omit<T, Keys>

### Union Manipulation Types
* Exclude<T, Excluded>
* NonNullable<T>
* Extract<T, Extracted>

### Function Types
* Parameters<Function>
* Return Type<Function>
* Awaited<Function>

### A note on interfaces vs types

We will be using both interfaces and types in this article. 

In TypeScript, the choice between using types and interfaces depends on the specific scenario. Interfaces are suited for describing the shape of objects, defining their properties, methods, and events. On the other hand, types are more versatile, allowing you to define primitive type aliases, tuples, unions, and work with functions, complex types, and mapped types. Ultimately, the choice between types and interfaces depends on the specific use case and the developer's judgment.

## 1. Partial<T>
`Partial` allows you to create a type where all properties of type `T` are made optional. This is like transforming a strict "must-have" checklist into a more relaxed "choose what you want."

**When it's useful:** When dealing with forms or data that might not have all fields filled in.

```typescript
interface Pizza {
  size: string;
  toppings: string[];
  delivery: boolean;
}

type PartialPizza = Partial<Pizza>;

const customPizza: PartialPizza = {
  size: "Large",
  toppings: ["Pepperoni", "Mushrooms"],
};
```

## 2. Required<T>
`Required` enforces that all properties of type `T` must be present. It's like making sure you have all the key ingredients for your favorite dish.

**When it's useful:** When you want to ensure certain properties are always included in your data.

```typescript
type RequiredPizza = Required<PartialPizza>;

const completePizza: RequiredPizza = {
  size: "Medium",
  toppings: ["Cheese", "Tomato"],
  delivery: true,
};
```

## 3. Readonly<T>
`Readonly` creates a type where all properties of type `T` are read-only. Think of it like a "no-touch" policy for your object properties.

**When it's useful:** When you want to prevent accidental changes to your data.

```typescript
type ReadonlyPizza = Readonly<Pizza>;

const classicPizza: ReadonlyPizza = {
  size: "Small",
  toppings: ["Olives", "Onions"],
  delivery: false,
};
```

## 4. Pick<T, Keys>
`Pick` helps you extract a type containing only the specified properties from type `T`. Think of it as creating a custom mini-version of an object.

**When it's useful:** When you want to narrow down your object's properties to just the essentials.

```typescript
type EssentialPizza = Pick<Pizza, 'size' | 'toppings'>;

const simplePizza: EssentialPizza = {
  size: "Medium",
  toppings: ["Tomatoes", "Cheese"],
};
```

## 5. Record<Keys, Value>
`Record` creates a type with specified keys of type `Keys` and values of type `Value`. Imagine building your own dictionary of words and their meanings.

**When it's useful:** When you need to create a structured mapping between keys and values of specific type.

```typescript
type FruitDictionary = Record<string, string>;

const fruits: FruitDictionary = {
  mango: "Yellow and delicious",
  apple: "Red and round",
};
```

## 6. Omit<T, Keys>
`Omit` creates a type by excluding specific properties listed in `Keys` from type `T`. It's like editing out parts of a picture you don't need.

**When it's useful:** When you want to remove certain properties from an object type.

```typescript
type PizzaWithoutDelivery = Omit<Pizza, 'delivery'>;

const inPersonPizza: PizzaWithoutDelivery = {
  size: "Large",
  toppings: ["Bacon", "Jalapeno"],
};
```

## 7. Exclude<T, Excluded>
`Exclude` filters out specific types from a union that are assignable to the excluded type. It's like saying "I only want one type of snack."

**When it's useful:** When you want to narrow down the possible values of a union type.

```typescript
type SnackOptions = "Cookies" | "Fruit";

// Excluding "Cookies" for a guest with dietary restrictions
type GuestSnackOptions = Exclude<SnackOptions, "Cookies">;

const guest1: GuestSnackOptions = "Fruit"; // Valid
const guest2: GuestSnackOptions = "Cookies";   // Error: 'Chips' is excluded

```

## 8. NonNullable<T>
`NonNullable` ensures a value is not `null` or `undefined` within a union. It's your way of saying "I want the real thing!"

**When it's useful:** When you need to ensure a value isn't missing in a union type.

```typescript
type NullableValue = string | null | undefined;
type SureValue = NonNullable<NullableValue>;
//same as type SureValue = string;

const certainValue: SureValue = "Absolutely!";
```

## 9. Extract<T, Extracted>
`Extract` retains only the types from a union that are assignable to the extracted type. It's like fishing out your favorite items from a mixed bag of snacks.

**When it's useful:** When you want to extract specific types from a union type.

```typescript
type SweetFruit = Extract<string | number | boolean, string>;
//same as type SweetFruit = string;

const favoriteFruit: SweetFruit = "Mango"; // Delicious choice!
```



## 10. Parameters<Function>
`Parameters` captures the parameter types of a function.

**When it's useful:** When you need to work with the types of function parameters.

```typescript
function cookDinner(mainCourse: string, sideDish: string): void {
  console.log(`Tonight's menu: ${mainCourse} with a side of ${sideDish}`);
}

type DinnerIngredients = Parameters<typeof cookDinner>;

const tonight: DinnerIngredients = ["Spaghetti", "Salad"];
```

## 11. ReturnType<Function>
`ReturnType` captures the return type of a function. It's like knowing what you're getting as the final dish.

**When it's useful:** When you want to know what type of value a function will return.

```typescript
function bakeCake(): string {
  return "A delicious cake!";
}

type CakeType = ReturnType<typeof bakeCake>;

const cake: CakeType = "Chocolate"; // Yum, cake time!
```

## 12. Awaited<Function>
`Awaited` extracts the resolved type of a promise returned by a function. It's like getting the actual meal after waiting for your order.

**When it's useful:** When you're working with promises and want to know what they will resolve to.

```typescript
async function fetchMeal(): Promise<string> {
  const response = await fetch("https://api.example.com/meal");
  return response.text();
}

type DeliveredMeal = Awaited<typeof fetchMeal>;

async function enjoyMeal(): Promise<void> {
  const meal: DeliveredMeal = await fetchMeal();
  console.log(`Enjoying a delicious meal: ${meal}`);
}
```

## Bonus: Mutable<T>
`Mutable` creates a type where all properties of type `T` are mutable. TypeScript does not offer this type out-of-the-box, so we have to create it ourselves.

**When it's useful:** When you've got a read-only object but need to make changes to it.

```typescript
type Mutable<T> = {
  -readonly [P in keyof T]: T[P];
};

type MutablePizza = Mutable<ReadonlyPizza>;

const dynamicPizza: MutablePizza = {
  size: "Extra Large",
  toppings: ["Jalapenos", "Tomatoes"],
  delivery: true,
};
```

## Combination Example
Let's explore some examples by integrating various utility types in TypeScript. We'll adopt a theme of assembling a superhero team with diverse abilities. Using utility types, we'll model different facets of this scenario.

```typescript
// Step 1: Define the types of superhero abilities
type Ability = "Flight" | "Strength" | "Invisibility" | "Telekinesis";

// Step 2: Create utility types for different types of superheroes
type SuperheroBase = {
  name: string;
  abilities: Ability[];
};

type FlyingSuperhero = Required<Pick<SuperheroBase, "name">> & {
  abilities: ["Flight"];
};

type StrongSuperhero = Required<Pick<SuperheroBase, "name">> & {
  abilities: ["Strength"];
};

type InvisibleSuperhero = Required<Pick<SuperheroBase, "name">> & {
  abilities: ["Invisibility"];
};

type TelekineticSuperhero = Required<Pick<SuperheroBase, "name">> & {
  abilities: ["Telekinesis"];
};

// Step 3: Create some superhero instances using the utility types
const superman: FlyingSuperhero = {
  name: "Superman",
  abilities: ["Flight"],
};

const hulk: StrongSuperhero = {
  name: "Hulk",
  abilities: ["Strength"],
};

const invisibleWoman: InvisibleSuperhero = {
  name: "Invisible Woman",
  abilities: ["Invisibility"],
};

const jeanGrey: TelekineticSuperhero = {
  name: "Jean Grey",
  abilities: ["Telekinesis"],
};

// Step 4: Create a superhero team with various abilities
type SuperheroTeam = FlyingSuperhero | StrongSuperhero | InvisibleSuperhero | TelekineticSuperhero;

const mySuperheroTeam: SuperheroTeam[] = [superman, hulk, invisibleWoman, jeanGrey];

// Step 5: Define an async action that our superheroes can perform
async function performHeroicAction(hero: SuperheroTeam): Promise<string> {
  switch (hero.abilities[0]) {
    case "Flight":
      return "Rescued people from a tall building.";
    case "Strength":
      return "Lifted a fallen bridge to save civilians.";
    case "Invisibility":
      return "Sneaked past the enemy to gather information.";
    case "Telekinesis":
      return "Stopped a speeding car using telekinetic power.";
    default:
      return "Performed a mysterious heroic feat.";
  }
}

// Step 6: Let our superheroes perform actions and communicate asynchronously
async function superheroTeamAssemble(team: SuperheroTeam[]): Promise<void> {
  for (const hero of team) {
    const actionResult: Awaited<ReturnType<typeof performHeroicAction>> = await performHeroicAction(hero);
    const introduction = `${hero.name}: "${actionResult}"`;
    console.log(introduction);
  }
}

// Step 7: Assemble the superhero team and let them perform heroic actions
superheroTeamAssemble(mySuperheroTeam);
```

> It's important to note that the use of `Awaited` and `ReturnType` here is done explicitly to show you how they can be used together. TypeScript is usually smart enough to recognize the type of a promise's resolved value and apply that type inference on it's own.

This example demonstrates how you can leverage TypeScript utility types to model more complex scenarios involving asynchronous communication, asynchronous actions, and dynamic type analysis. It also adds an extra layer of fun and playfulness to our superhero team example! 🦸‍♂️🦸‍♀️🚀

## Conclusion
TypeScript's utility types are built-in type manipulators that facilitate easy alterations and actions on established types. I hope you found these TypeScript utility types examples informative. This isn't the end; you can leverage these utility types to craft your own custom types. Using utility types can significantly enhance your code, making it more reusable, concise, and clean.

For more information about how you can use Cosmic in your application with Typescript support out-of-the-box, visit our [documentation](https://www.cosmicjs.com/docs) or [get started with Cosmic](https://app.cosmicjs.com/signup).