TypeScript Utility Types

Note: If you're new to TypeScript, check our Getting Started with TypeScript tutorial first.


TypeScript utility types are built-in helpers that make it easier to work with and transform data types. They are particularly useful for working with objects and union types.

Utility types help you write safer, shorter, and more flexible code by enabling you to create new types from existing ones. These new types may differ from the original object or type by

  • making properties optional/required/readonly, or
  • picking and omitting fields, or
  • adding new features to the existing type.

Used Utility Types

Some common utility types are listed in the table below:

Utility Type Description
Partial<Type> Makes all properties optional.
Required<Type> Makes all properties required.
Readonly<Type> Makes all properties read-only.
Pick<Type, Keys> Keeps only certain properties.
Omit<Type, Keys> Removes certain properties.
Record<Keys, Type> Creates objects with specific keys and types.
Exclude<UnionType, ExcludedMembers> Removes specific types from a union.
Extract<Type, Union> Extracts specific types from a union.
ReturnType<Type> Gets the return type of a function.
Parameters<Type> Gets the parameter types of a function as a tuple.
ConstructorParameters<Type> Gets the parameter types of the constructor of a class.

Now, let's look at these utility types individually.


1. Partial

The Partial type makes all properties in a type optional. Use it when you only want some properties from an object.

Example

// Interface with 3 required properties
interface Student {
    id: number;
    name: string;
    gpa: number;
}

// Create a partial type from Student
// All Student properties are now optional
type PartialStudent = Partial<Student>;

// Object with only id property
let student1: PartialStudent = {id: 101};
console.log(student1);

// Object with only name and gpa properties
let student2: PartialStudent = {name: "Peter Parker", gpa: 4};
console.log(student2);

// Object with only id and gpa properties
let student3: Partial<Student> = {id: 105, gpa: 3.5};
console.log(student3);

Output

{ id: 101 }
{ name: 'Peter Parker', gpa: 4 }
{ id: 105, gpa: 3.5 }

Here, the Student interface has three required properties: id, name, and gpa. Thus, you can't create any Student object that doesn't include these properties:

// Error: name and gpa are missing
let student: Student = {id: 101};

However, we can accomplish this task by creating a partial type from Student:

type PartialStudent = Partial<Student>;

So, PartialStudent is like a version of the Student type, but with all its properties being optional.

Notice the following code in our program:

let student3: Partial<Student> = {id: 105, gpa: 3.5};

This code is equivalent to

let student3: PartialStudent = {id: 105, gpa: 3.5};

2. Required

The Required type makes all properties in a type required. Use it when all properties are required.

Example

// Interface with optional properties id and gpa
interface Student {
    id?: number;
    name: string;
    gpa?: number;
}

// Create a required type from Student
// All Student properties are now required
type RequiredStudent = Required<Student>;

// Create object of RequiredStudent type
let student1: RequiredStudent = {id: 101, name: "Peter Parker", gpa: 4};
console.log(student1);

// Error: RequiredStudent object initialized without id property
// let student2: RequiredStudent = {name: "Peter Parker", gpa: 4};

Output

{ id: 101, name: 'Peter Parker', gpa: 4 }

Here, the Student interface has two optional properties: id and gpa.

However, the RequiredStudent type makes these properties required:

type RequiredStudent = Required<Student>;

Thus, you can't create an object like this:

// Error: RequiredStudent object initialized without id property
let student2: RequiredStudent = {name: "Peter Parker", gpa: 4};

3. Readonly

The Readonly type makes all properties in a type read-only (they can't be changed). Use it when you need to protect your data from accidental change.

Example

type Student = {
    id: number;
    name: string;
}

// Use Readonly to create an object
let student1: Readonly<Student> = {id: 101, name: "Peter Parker"};
console.log(student1);

// Error: 'name' is a read-only property
// student1.name = "Gwen Stacy";

Output

{ id: 101, name: 'Peter Parker' }

Here, student1 is of type Readonly<Student>.

let student1: Readonly<Student> = {id: 101, name: "Peter Parker"};

So, its properties cannot be changed once it's been initialized:

// Error: 'name' is a read-only property
student1.name = "Gwen Stacy";

4. Pick

Pick creates a type with only the specified properties. Use it when you only need a few fields from a bigger type.

Example

type Student = {
    id: number;
    name: string;
    gpa: number;
    phone: number;
}

// Create a type that only has id and gpa
type IdGPA = Pick<Student, "id" | "gpa">;

// Create another type that only has name
type StudentName = Pick<Student, "name">;

// Create object of IdGPA type
let student1: IdGPA = {id: 101, gpa: 2.6};
console.log(student1);

// Create object of StudentName type
let student2: StudentName = {name: "Peter Parker"};
console.log(student2);

Output

{ id: 101, gpa: 2.6 }
{ name: 'Peter Parker' }

Here, the Student type has four properties: id, name, gpa, and phone.

We then created two different types from Student:

  • IdGPA - Only contains the id and gpa properties.
  • StudentName - Only contains the name property.
type IdGPA = Pick<Student, "id" | "gpa">;
type StudentName = Pick<Student, "name">;

As you can see, you can add further properties in your Pick type by using the | operator.


5. Omit

Omit creates a type by removing the specified properties.

Example

type Student = {
    id: number;
    name: string;
    gpa: number;
    phone: number;
}

// Create a type that doesn't have id
type NoId = Omit<Student, "id">;

// Create another type that doesn't have gpa and phone
type IdName = Omit<Student, "gpa" | "phone">;

// Create object of NoId type
let student1: NoId = {name: "Peter Parker", gpa: 4, phone: 5786389};
console.log(student1);

// Create object of IdName type
let student2: IdName = {id: 103, name: "Gwen Stacy"};
console.log(student2);

Output

{ name: 'Peter Parker', gpa: 4, phone: 5786389 }
{ id: 103, name: 'Gwen Stacy' }

Here, the Student type has four properties: id, name, gpa, and phone.

We then created two different types from Student:

  • NoId - Omits the id property. Thus, it only contains the name, gpa, and phone properties.
  • IdName - Omits the gpa and phone properties. Thus, it only contains the id and name properties.
type NoId = Omit<Student, "id">;
type IdName = Omit<Student, "gpa" | "phone">;

6. Record

Record creates an object type with specific keys and value types. You use it when you know all the keys and want them to follow a pattern.

Example

type Party = "Democrat" | "Republican" | "Independent";

// New type where Party values are keys
// Each key has a numerical value
type VoteCount = Record<Party, number>;

let presidentialVote: VoteCount = {
    Democrat: 71,
    Republican: 96,
    Independent: 2
};

console.log(presidentialVote);

Output

{ Democrat: 71, Republican: 96, Independent: 2 }

Here, the Party type can have one of three values: "Democrat", "Republican", or "Independent".

Then, we created a VoteCount type using Record, which stores key-value pairs in the following way:

  • Keys: The values permitted by the Party type serve as the keys.
  • Values: Each key is assigned a numerical value.
type VoteCount = Record<Party, number>;

We then created a presidentialVote object of VoteCount type:

let presidentialVote: VoteCount = {
    Democrat: 71,
    Republican: 96,
    Independent: 2
};

The key-value pairs of presidentialVote are given below:

Key Value
Democrat 71
Republican 96
Independent 2

7. Exclude

Exclude creates a type by removing specific types from a union. This type is useful for narrowing down possible values.

Example

type OrderStatus = "cart" | "bought" | "cancelled" | "error";

// Create a new type by excluding "error" status
type ValidStatus = Exclude<OrderStatus, "error">;

// Valid code
let order1: ValidStatus = "bought";
let order2: ValidStatus = "cancelled";
let order3: ValidStatus = "cart";

// Invalid code because "error" doesn't exist in ValidStatus
// let order4: ValidStatus = "error";

console.log(order1);
console.log(order2);
console.log(order3);

Output

bought
cancelled
cart

Here, the ValidStatus type has all the values of OrderStatus except for the "error" type

type OrderStatus = "cart" | "bought" | "cancelled" | "error";

type ValidStatus = Exclude<OrderStatus, "error">;

You can add further exclusions with the | operator:

Exclude<OrderStatus, "cancelled" | "error">;

The above code excludes "cancelled" and "error".


8. Extract

Extract creates a type by extracting specific types from a union. This type is useful for narrowing down possible values.

Example

type OrderStatus = "cart" | "bought" | "cancelled" | "error";

// Create a new type by extracting "bought" status
type Bought = Extract<OrderStatus, "bought">;

// Create object of Bought type
let order1: Bought = "bought";
console.log(order1);

// Invalid: "cart" is absent in Bought type
// let order2: Bought = "cart";

// Output: bought

Here, the Bought type can only store "bought":

type OrderStatus = "cart" | "bought" | "cancelled" | "error";

type Bought = Extract<OrderStatus, "bought">;

You can extract further values with the | operator:

Extract<OrderStatus, "cancelled" | "error">;

The above code creates a type that can only store "cancelled" and "error".


9. ReturnType

ReturnType gets the return type of a function.

Example

function greet(name: string): string {
    return `Welcome, ${name}!`;
}

// Get the return type of greet() i.e. string
type GreetReturn = ReturnType<typeof greet>;

// Create a variable of GreetReturn type
let message: GreetReturn = greet("Lord Vader");
console.log(message);

Output

Welcome, Lord Vader!

Here, the greet() function returns a string. So, the GreetReturn type will be identical to the string type.

Therefore, the following code:

let message: GreetReturn = greet("Lord Vader");

is equivalent to

let message: string = greet("Lord Vader");

10. Parameters

The Parameters utility type gets the parameter types of a function as a tuple.

function greet(name: string, age: number): string {
    return `Welcome, ${name}. You are ${age} years old!`;
}

// GreetParams is a [string, number] tuple
type GreetParams = Parameters<typeof greet>;

// Create a variable of GreetParams type
let personInfo: GreetParams = ["Vader", 45];

// Print the tuple elements
console.log(personInfo[0]);
console.log(personInfo[1]);

// Pass the tuple elements as arguments to greet()
let message: string = greet(personInfo[0], personInfo[1]);
console.log(message);

Output

Vader
45
Welcome, Vader. You are 45 years old!

Here, the GreetParams type is a tuple consisting of parameter types of the greet() function.

Since the greet() function has a string and number parameter, GreetParams will be a tuple of [string, number] type.


11. ConstructorParameters

The ConstructorParameters type is similar to Parameters, except that it returns the parameter types of the constructor of an object.

Example

class Person {

    constructor(private name: string, private age: number) {}
    
    greet(): void {
        console.log(`Welcome, ${this.name}. You are ${this.age} years old!`);
    }
}

// PersonParams is a [string, number] tuple
type PersonParams = ConstructorParameters<typeof Person>;

// Create a variable of PersonParams type
let personInfo: PersonParams = ["Vader", 45];

// Print the tuple elements
console.log(personInfo[0]);
console.log(personInfo[1]);

// Create an instance of Person and pass the
// Tuple elements as arguments to the constructor
let person1 = new Person(personInfo[0], personInfo[1]);
person1.greet();

Output

Vader
45
Welcome, Vader. You are 45 years old!

Here, the PersonParams type is a tuple consisting of the constructor parameter types of Person.


More on TypeScript Utility Types

Does JavaScript have Utility Types?

No, JavaScript doesn't have utility types since it's a dynamically typed language. As such, JavaScript checks the types at runtime instead of compile time, which makes it difficult to catch buggy code.

By contrast, TypeScript checks the type at compile time. Thus, the utility types offered by TypeScript help narrow down the types you want to use in your code.

And if you use improper types, you can correct them at compile time itself since TypeScript will notify you of the error. For example,

JavaScript Code

// No type checks or utility types
let student = { id: 101, name: "Peter Parker" };

// Allowed, but may cause bugs
student.name = 42;

TypeScript Code

interface Student {
    id?: number;
    name?: string;
}

// Error at compile time because 'name' cannot be a number
let user: Required<Student> = { id: 101, name: 42 };
Advantages of Utility Types

Utility types have the following advantages:

  • Less Repetition: They help you avoid writing similar type definitions over and over.
  • Type Safety: TypeScript will catch mistakes early, before your code runs.
  • Flexibility: You can easily adjust types as your code changes.
Custom Utility Types

You can also create your own utility types in TypeScript. For example,

// Create a custom type
type CustomUnion<T> = T | boolean;

// Same as string | boolean
type StringBool = CustomUnion<string>;

// Same as number | boolean
type NumBool = CustomUnion<number>;

// StringBool can take string or boolean data
let var1: StringBool = "String Data";
let var2: StringBool = false;
console.log(var1);
console.log(var2);

// NumBool can take number or boolean data
let var3: NumBool = 75;
let var4: NumBool = true;
console.log(var3);
console.log(var4);

Output

String Data
false
75
true

In the above program, notice the following code:

type CustomUnion<T> = T | boolean;

Here, T is a placeholder for the type you want to specify, while the boolean type is always a part of the union. Thus,

  • CustomUnion<string> - Gives you a union type of string | boolean.
  • CustomUnion<number> - Gives you a union type of number | boolean.

You can also use additional placeholders as per your need. For example,

type CustomUnion<T, U> = T | U | boolean;

Also Read:

Did you find this article helpful?

Our premium learning platform, created with over a decade of experience and thousands of feedbacks.

Learn and improve your coding skills like never before.

Try Programiz PRO
  • Interactive Courses
  • Certificates
  • AI Help
  • 2000+ Challenges