DEV Community

Cover image for Typescript utility types that you must know
Arafat
Arafat

Posted on • Edited on

Typescript utility types that you must know

Hello everyone, Today, In this article, we will go through some beneficial and essential utility types in Typescript that can make your life easier.

Utility types in Typescript are some predefined generic types that can be used to manipulate or create other new types. These types are available globally in all Typescript projects, so you don't need to add any dependencies to get these going.

Table of contents

Partial

The first utility type we will look at is Partial, which, as It sounds, makes everything optional or partial.

Here is an example of Partial utility type:

interface Person {
  name: string;
  age: number;
  email: string;
}

// Define a new type called 'PartialPerson' that is a partial version of 'Person'
type PartialPerson = Partial<Person>;

// Same as:
// interface Person {
//   name?: string | undefined;
//   age?: number | undefined;
//   email?: string | undefined;
// }
Enter fullscreen mode Exit fullscreen mode

Required

The opposite to Partial is Required utility type, which makes everything required.

interface Person {
 name?: string | undefined;
 age?: number | undefined;
 email?: string | undefined;
}

// Define a new type called 'RequiredPerson' that is a required version of 'Person'
type RequiredPerson = Required<Person>;

// Same as:
// interface Person {
//   name: string;
//   age: number;
//   email: string;
// }
Enter fullscreen mode Exit fullscreen mode

Omit

You can use the Omit utility type to create a new type from an existing type, however, with some properties removed.

interface User {
  id: number;
  name: string;
  email: string;
  age: number;
}

type UserWithoutEmail = Omit<User, 'email'>;

// same as: 
// interface User {
//   id: string;
//   name: string;
//   age: number;
// }
Enter fullscreen mode Exit fullscreen mode

We can also remove multiple properties by passing an union

interface User {
  id: number;
  name: string;
  email: string;
  age: number;
}

type UserWithoutEmailAndName = Omit<User, 'email' | 'name'>;

// same as: 
// interface User {
//   id: string;
//   age: number;
// }
Enter fullscreen mode Exit fullscreen mode

Pick

The opposite of Omit is the Pick utility type that allows you to create a new type that contains only a subset of properties from an existing type.

interface User {
  id: number;
  name: string;
  email: string;
  age: number;
}

type UserWithEmailAndName = Pick<User, 'email' | 'name'>;

// same as: 
// interface User {
//   name: string;
//   email: string;
// }
Enter fullscreen mode Exit fullscreen mode

Multiple utility types together

We can even use multiple utility types together. For example:

interface User {
  id: number;
  name: string;
  email: string;
  age: number;
}

type PartialPick = Partial<Pick<User, 'email' | 'name'>>;

// same as: 
// interface User {
//   name?: string | undefined;
//   email?: string | undefined;
// }
Enter fullscreen mode Exit fullscreen mode

Another exmaple:

interface User {
  id: number;
  name: string;
  email: string;
  age: number;
}

type OmitPartialPick = Omit<Partial<Pick<User, 'email' | 'name'>>, 'email'>;

// same as: 
// interface User {
//   name?: string | undefined;
// }
Enter fullscreen mode Exit fullscreen mode

Readonly

Readonly utility types allow you to create a new type from an existing type set as readonly, which means we cannot modify any property after the initialization.

interface Person {
  id: number;
  name: string;
  age: number;
}

type ReadonlyPerson = Readonly<Person>;

// same as:
// interface Person {
//   readonly id: number;
//   readonly name: string;
//   readonly age: number;
// }

const person: ReadonlyPerson = {
  id: 1,
  name: 'John',
  age: 25
};

person.name = 'Mike'; // Error: Cannot assign to 'name' because it is a read-only property.

Enter fullscreen mode Exit fullscreen mode

Mutable

You can also create a Mutable type helper that allows you to convert all readonly types to mutable type.

interface Person {
  readonly id: number;
  readonly name: string;
  readonly age: number;
}

// The syntax for Mutable is as follows:
type Mutable<T> = {
  -readonly [P in keyof T]: T[P];
};

type MutablePerson = Mutable<Person>;

// same as:
// interface Person {
//   id: number;
//   name: string;
//   age: number;
// }

const person: MutablePerson = {
  id: 1,
  name: 'John',
  age: 25
};

person.name = 'Mike'; // Okay
person.id = 2; // Okay
Enter fullscreen mode Exit fullscreen mode

Exclude

Exclude utility type allows you to create a new type by removing members of an union

type NumberOrString = number | string;
type OnlyNumber = Exclude<NumberOrString, string>;

// same as:
// type OnlyNumber = number;

const num: OnlyNumber = 5;
const str: OnlyNumber = 'hello'; // Error: Type '"hello"' is not assignable to type 'number'.
Enter fullscreen mode Exit fullscreen mode

You can even exlude mulitple members of an union:

type NumberStringOrBoolean = number | string | boolean;
type OnlyBoolean = Exclude<NumberStringOrBoolean, string | number>;

// same as:
// type OnlyBoolean = boolean;
Enter fullscreen mode Exit fullscreen mode

Extract

The opposite to Exclude is Extract utitlity type that allows you to pick a or multiple members from an union:

type NumberOrString = number | string | boolean;
type OnlyNumber = Extract<NumberOrString, number>;

// same as:
// type OnlyNumber = number;
Enter fullscreen mode Exit fullscreen mode

ReturnType

ReturnType utility type lets you to extract the return type of a function type. It takes a function type as an argument and returns the value type that the function returns.

function add(a: number, b: number): number {
  return a + b;
}

type AddReturnType = ReturnType<typeof add>;
// type AddReturnType = number;

// ---

function addStr(a: string, b: string): string{
  return a + b;
}

type AddReturnType2 = ReturnType<typeof addStr>;
// type AddReturnType2 = string;
Enter fullscreen mode Exit fullscreen mode

Parameters

The Parameters utility type lets you extract the type of parameters from a function.

function add(a: number, b: string, c:boolean): string {
  return a + b;
}

type AddReturnType = Parameters<typeof add>;
// type AddReturnType = [a: number, b: string, c:boolean];
Enter fullscreen mode Exit fullscreen mode

NonNullable

NonNullable utility type lets you to create a new type by excluding null and undefined from a given type.

type NullableString = string | null | undefined;

type NonNullableString = NonNullable<NullableString>;
// type NonNullableString = string;

const str1: NullableString = null;
const str2: NonNullableString = 'hello';
const str3: NonNullableString = null; // Error: Type 'null' is not assignable to type 'string'.
Enter fullscreen mode Exit fullscreen mode

Awaited

Awaited utility type allows you to extract the resolved type of a promise or other type that uses await.

type promiseNumber = Promise<number>

type justNumber = Awaited<Promise<number>>
// type justNumber = number
Enter fullscreen mode Exit fullscreen mode

Awaited and ReturnType combined

Here's an example of using ReturnType and Awaited together:

async function fetchData(): Promise<string> {
  // fetch data from API and return a string
}

type ResolvedResult = Awaited<ReturnType<typeof fetchData>>;
// type ResolvedResult = string
Enter fullscreen mode Exit fullscreen mode

In this example, we define an async function that returns a Promise of a string (Promise<string>). We then use ReturnType to extract the return type of fetchData and pass it as an argument to Awaited to unwrap the promised's resolved type.


Conclusion

These are some of the most used typescript utility types that are heavily used by other developers worldwide. It cleans your code and can be used to work with types more expressively and concisely. I hope you will find this article helpful, and if you think I missed any essential utility types, please let me know in the comments. Thanks for reading the article. See you all in my next article🐼.

Visit:
👨‍💻My Portfolio
🏞️My Fiverr
🌉My Github

Top comments (16)

Collapse
 
gautamvaishnav profile image
Gautam Vaishnav

You are a great developer surly

Collapse
 
arafat4693 profile image
Arafat

Thanks for your nice comment😊.

Collapse
 
hexram profile image
Héctor Ramírez

Concise and to the point. I foresee a terrific developer in you...

Collapse
 
shu2210 profile image
s_yasunaga

Great post!
May I translate this article into Japanese and post it to community? (I'll definitely link this)

Collapse
 
arafat4693 profile image
Arafat

Sure, Go ahead!!!

Collapse
 
kristiyan_velkov profile image
Kristiyan Velkov

Take your TypeScript skills to new heights with "Mastering TypeScript Core Utility Types":

📖 Buy on Leanpub
📖 Buy on Amazon

Collapse
 
kristiyan_velkov profile image
Kristiyan Velkov

If you’re looking to deepen your understanding of TypeScript utility types like Partial or Readonly, I recently wrote a book, Mastering TypeScript Core Utility Types, that covers these and more with practical examples and exercises. Feel free to check it out! 😊 👉 Here

Collapse
 
bybydev profile image
byby.dev

Hot take: don't use any TS utility types :v

Collapse
 
voxpelli profile image
Pelle Wessman

Why? What would be the reason to avoid them?

Collapse
 
bybydev profile image
byby.dev

Over using utility types may lead to code that is difficult to understand and maintain. Especially nesting many types together. I know this is a soft opinion, the point is I saw many projects with very complex type definitions. Here's an example:

type User = {
  id: number;
  name: string;
  email: string;
};

type VeryComplexUser = Readonly<Pick<User, "id">> &
  Partial<Omit<User, "email">> &
  { email: User["email"] | null } &
  { role: string } &
  Record<"email", string> & {
    role: User["name"] extends "admin" ? "admin" : "user";
  } & {
    name: User["name"];
  } & {
    id: { [K in keyof User["id"]]: User["id"][K] };
  } & {
    email: User["email"] & string;
  } & {
    role: Exclude<User["role"], "guest">;
  };
Enter fullscreen mode Exit fullscreen mode
Thread Thread
 
voxpelli profile image
Pelle Wessman

I think the main problem here is the overuse of intersections, something that can also be a performance issue.

Collapse
 
voxpelli profile image
Pelle Wessman

And when the built in utility types are not enough, then there’s the type-fest module with a ton more ones: github.com/sindresorhus/type-fest

Collapse
 
jannisdev profile image
Jannis

Awesome post! Very helpful and those utility types are just awesome! 🔥

Collapse
 
salonidobhal13 profile image
Saloni Dobhal

Really good and helpful..!!

Collapse
 
abhidadhaniya23 profile image
Abhi Dadhaniya

Thanks for this article, very easy to understand 😊