TypeScript provides a lot of utilities to help us write better, more scalable, better-structured, and overall safer applications.

One of these utilities comes under the form of Utility Types, which we’ll discuss in this article.

The concept of Utility Types in TypeScript is tightly tied with that of Generics, so if you aren’t already familiar with that, I highly recommend you read this article before and then return to this one.

What Is a Utility Type?

In our context, when we talk about utility types, we talk about the Generics that TypeScript provides us with globally so that we can transform or enhance our types.

A Utility Type takes one or two generic types as arguments and gives us a transformed type based on that initial type. We’re usually annotating the arguments with the letters:

  • T, which stands for Type (Or the initial type)
  • K, which generally stands for Key
  • U, which generally stands for Union

You don’t have to use those letters, nor do you have to use letters at all!

You can use words if that makes it easier for you; however, remember that those are the standard annotations you’ll come across in most TypeScript codebases wherever you deal with Generics.

Each Utility Type comes with its own unique way of transforming our types, as well as, you’ve guessed it – different use cases.

So, with all of that said, let’s see what the most important 12 Utility Types in TypeScript are:

Readonly<T>

The Readonly modifier allows us to make all of a type’s properties and its nested properties read-only, thus, not allowing us to change their respective values once defined or assigned.

You might say that it’s redundant since we already have the const keyword for that.

That’s true; however, that only applies to primitive values, not objects’ properties; that’s because they are reference values.

type Person = {
  firstName: string;
  lastName: string;
};

const person: Readonly<Person> = {
  firstName: 'Sarah',
  lastName: 'McKnight',
};

person.firstName = 'Freya'; // Compilation Error

ReadonlyArray<T>

The ReadonlyArray utility makes sure that we restrict one’s ability to mutate an original array whose type is that of an ReadonlyArray<T>:

type MyReadonlyNumericArray = ReadonlyArray<number>;

const myArr: MyReadonlyNumericArray = [1, 2, 3];

myArr.push(4); // Will throw a compilation error

myArr.map((val) => val * 2); // Compiles, since we are not mutating the original array, but rather returning a new one

ReturnType<T>

The ReturnType utility takes as its argument the type of a function and will give us the type of that function’s return type.

const stringifyNumber = (arg: number): string => arg.toString();

type FunctionReturnType = ReturnType<typeof stringifyNumber>; // string

We can also use the utility for methods inside classes using the ReturnType<MyClass['myFunctionName']> syntax.

Partial<T>

The Partial utility allows us to make a type’s properties and any nested properties optional, thus, allowing us to omit any of them when working with the transformed type.

type ContactInformation = {
  facebook: string;
  twitter: string;
  instagram: string;
};

type Customer = {
  firstName: string;
  lastName: string;
  contactInformation: Partial<ContactInformation>;
}; 

const customer: Customer = {
  firstName: 'John',
  lastName: 'Carter',
  contactInformation: {}, // Works just fine
};

Required<T>

As opposed to Partial, Required makes all the properties of a generic type T required.

type ContactInformation = {
  facebook: string;
  twitter: string;
  instagram: string;
};

type Customer = {
  firstName: string;
  lastName: string;
  contactInformation: Required<ContactInformation>;
}; 

const customer: Customer = {
  firstName: 'John',
  lastName: 'Carter',
  contactInformation: { // Compilation Error, since there's no `instagram` value given
    facebook: 'some-facebook-address',
    twitter: 'some-twitter-address',
  },
};

NonNullable<T>

The NonNullable utility ensures that the value given to a parameter of a function can be neither null, nor undefined. This utility complements the strictNullChecks compiler flag that’s set up in the root tsconfig.json file.

function logger<T>(valueToLog: NonNullable<T>) {
  console.log(JSON.stringify(valueToLog));
}

print('Hey');

print({ x: '2' });

print(null); // Compilation Error

print(undefined); // Compilation Error

Pick<T, K>

The Pick utility allows us to “pick” specific keys or properties from a type; they will be separated by the | sign.

type MyType = {
  interestingKey: string;
  someKey: SomeType;
  someOtherKey: string;
  anotherInterestingKey: string;
};

type MyInterestingType = Pick<MyType, 'interestingKey' | 'anotherInterestingKey'>;

type MyBoringType = Pick<MyType, 'someKey' | 'someOtherKey'>;

const myInterestingObject: MyInterestingType = {
  interestingKey: 'interesting value',
  anotherInterestingKey: 'another interesting value',
};

const myBoringObject: MyBoringType = { // Will throw a compilation error since we haven't added the `someOtherKey` property to our object
  someKey: 'some value',
};

Omit<T, K>

Similarly to Pick, the Omit utility type expects two generics parameters:

  • The type
  • The property keys that wish to omit

However, the keys we’ll specify will be removed from the returned type this time.

type Person = {
  firstName: string;
  lastName: string;
  age: number;
}

type PersonWithoutAge = Omit<Person, 'age'>;

Record<K, T>

The Record utility allows us to create an object whose keys are K and values are T. It’s helpful to map a type’s properties to another type.

type GenericObject = Record<string, any>; // Will be an object whose values can be represented by any value type

type Person = Record<'firstName' | 'lastName', string>; // Equivalent to { firstName: string; lastName: string; }

The real benefit of this Generic comes when having to deal with other generic types; for example, we might have a function that transforms all the values of an object into strings:

function allValuesToString<T>(obj: T): Record<keyof T, string> {
    let transfer: Partial<Record<keyof T, string>> = {};
    
    Object.keys(obj).forEach((key) => {
      transfer[key] = JSON.stringify(obj[key]);
    });
    
    return transfer as Record<keyof T, string>;
}

const car = {
  make: 'Audi',
  model: 'R8',
  horsepower: 562
}

type Car = typeof car;

const strCar = allValuesToString(car);

Extract<T, U>

The Extract utility takes two generic parameters:

  • A type
  • A Union

It constructs a type by extracting all the union members from T that are assignable to U:

type MyFirstUnion = "a" | "b" | "c";

type MySecondUnion = "a" | "c" | "d";

type MyType = Extract<MyFirstUnion, MySecondUnion>; // "a" | "d"

Exclude<T, U>

As opposed to Extract, the Exclude generic utility type will extract all of the union members of T that are not assignable to U:

type MyFirstUnion = "a" | "b" | "c";

type MySecondUnion = "a" | "c" | "d";

type MyType = Exclude<MyFirstUnion, MySecondUnion>; // "b"

Summary

So that’s pretty much all in terms of the major Utility Types that TypeScript provides;

Of course, there are also a handful of Generics that we’ve been left out due to the unlikeliness of you needing them in your day-to-day work; however, if you want to check them out due to personal curiosity, you can find them here.

With all of that said, I hope you’ve enjoyed the read!

If you have any feedback, be sure to leave a comment below!

Cheers!

👋 Hey, I'm Vlad Mihet
I'm Vlad Mihet, a blogger & Full-Stack Engineer who loves teaching others and helping small businesses develop and improve their technical solutions & digital presence.

💬 Leave a comment

Your email address will not be published.

We will never share your email with anyone else.