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 theid
andgpa
properties.StudentName
- Only contains thename
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 theid
property. Thus, it only contains thename
,gpa
, andphone
properties.IdName
- Omits thegpa
andphone
properties. Thus, it only contains theid
andname
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
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 };
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.
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 ofstring | boolean
.CustomUnion<number>
- Gives you a union type ofnumber | boolean
.
You can also use additional placeholders as per your need. For example,
type CustomUnion<T, U> = T | U | boolean;
Also Read: