DEV Community

Cover image for Advanced TypeScript Utility Types in Detail
Bhat Aasim
Bhat Aasim

Posted on

Advanced TypeScript Utility Types in Detail

Advanced Typescript Utility Types

TypeScript provides a set of utility types that help transform or manipulate types more flexibly and powerfully. These types are useful for building more dynamic and reusable codebases. These utility types are built-in and can be used to create new types based on existing ones. In this article, we'll explore some of the advanced TypeScript utility types and how they can be used to improve your code.

1. Partial<Type>

The Partial utility type makes all properties of the given type T optional. It is useful when you want to work with objects where only some properties may be provided, or when you're working with optional values in forms or updates.

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

// All properties are now optional
type PartialUser = Partial<User>;

function updateUser(id: number, user: PartialUser) {
    // Update user properties
}

updateUser(id: 1, { name: "Aasim" });
Enter fullscreen mode Exit fullscreen mode

2. Required<Type>

The Required utility type does the opposite of Partial. It makes all properties of the given type T required, ensuring that all properties must be present.

interface User {
    id: number;
    name?: string; // Optional property
    email?: string; // Optional property
}

// All properties are now required
type RequiredUser = Required<User>;

const user: RequiredUser = {
    id: 1,
    name: "Aasim",
    email: "contact@bhataasim.com",
};
Enter fullscreen mode Exit fullscreen mode

3. Readonly<Type>

The Readonly utility type makes all properties of the given type T readonly, meaning they cannot be modified once they are created. It is useful when you want to prevent accidental changes to objects.

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

// All properties are now readonly
type ReadonlyUser = Readonly<User>;

const user: ReadonlyUser = {
    id: 1,
    name: "Aasim",
    email: "contact@bhataasim.com",
};

// Error: Cannot assign to 'name' because it is a read-only property
user.name = "Bhat Aasim";
Enter fullscreen mode Exit fullscreen mode

4. Pick<Type, Keys>

The Pick utility type allows you to create a new type by selecting only a subset of properties from the given type T. It is useful when you want to create a new type with only specific properties.

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

// Select only 'id' and 'name' properties
type UserBasicInfo = Pick<User, "id" | "name">;

const user: UserBasicInfo = {
    id: 1,
    name: "Aasim",
};
Enter fullscreen mode Exit fullscreen mode

5. Omit<Type, Keys>

The Omit utility type is the opposite of Pick. It allows you to create a new type by excluding a subset of properties from the given type T. It is useful when you want to create a new type without specific properties.

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

// Exclude 'id' and 'email' properties
type UserPersonalInfo = Omit<User, "id" | "email">;

const user: UserPersonalInfo = {
    name: "Aasim",
    age: 21,
};
Enter fullscreen mode Exit fullscreen mode

6. Exclude<Type, ExcludedUnion>

The Exclude utility type removes all types from Type that are assignable to ExcludedUnion. It is useful when you want to exclude certain types from a union.

type Status = "active" | "inactive" | "pending";

type ActiveStatus = Exclude<Status, "pending">;

const status: ActiveStatus = "active";

// Error: Type '"pending"' is not assignable to type 'ActiveStatus'.
const status: ActiveStatus = "pending";
Enter fullscreen mode Exit fullscreen mode

7. Extract<Type, Union>

Extract does the opposite of Exclude. It constructs a type by extracting all types from Type that are assignable to Union. It is useful when you want to extract certain types from a union.

type Status = "active" | "inactive" | "pending";

type ActiveStatus = Extract<Status, 'active' | 'inactive'>;

const status: ActiveStatus = "active";

// Error: Type '"pending"' is not assignable to type 'ActiveStatus'.
const status: ActiveStatus = "pending";
Enter fullscreen mode Exit fullscreen mode

8. Record<Keys, Type>

The Record utility type constructs an object type whose property keys are Keys and whose property values are Type. It is useful when you want to create an object type with specific keys and values. It is commonly used to create dictionaries or maps in TypeScript.

// example 1
type Roles = "admin" | "user" | "guest";

type UserRoles = Record<Roles, boolean>;

const roles: UserRoles = {
    admin: true,
    user: true,
    guest: false,
};

// example 2
type PageInfo = {
    title: string,
    url: string,
};

type Page = 'home' | 'about' | 'contact';

const pages: Record<Page, PageInfo> = {
    home: { title: 'Home', url: '/' },
    about: { title: 'About', url: '/about' },
    contact: { title: 'Contact', url: '/contact' },
};
Enter fullscreen mode Exit fullscreen mode

9. NonNullable<Type>

The NonNullable utility type removes null and undefined from the given type T. It is useful when you want to ensure that a value is not null or undefined.

type User = {
    id: number;
    name: string | null;
};

// Remove null and undefined
type NonNullableUser = NonNullable<User>;

const user1: NonNullableUser = {
    id: 1,
    name: "Aasim",
};

const user2: NonNullableUser = {
    id: 2,
    name: null, // Error: Type 'null' is not assignable to type 'string'.
};
Enter fullscreen mode Exit fullscreen mode

10. ReturnType<Type>

The ReturnType utility type extracts the return type of a function type. It is useful when you want to get the return type of a function dynamically.

function getUser() {
    return { id: 1, name: "Aasim" };
}

type User = ReturnType<typeof getUser>;

const user: User = {
    id: 1,
    name: "Aasim",
};
Enter fullscreen mode Exit fullscreen mode

11. Parameters<Type>

The Parameters utility type extracts the parameter types of a function type as a tuple. It is useful when you want to get the parameter types of a function dynamically.

function loginUser(username: string, password: string) {
    // Login logic
}

type LoginParams = Parameters<typeof loginUser>;

function authenticateUser(...params: LoginParams) {
    // Authenticate user
}

authenticateUser("admin", "password");

authenticateUser("user", 123); // Error: Argument of type 'number' is not assignable to parameter of type 'string'.
Enter fullscreen mode Exit fullscreen mode

Conclusion

These are some of the advanced TypeScript utility types that can help you write more robust and flexible code. By leveraging these utility types, you can create more dynamic and reusable types in your TypeScript projects.

Checkout the Official TypeScript Documentation for more utility types: TypeScript Utility Types

I hope you found this article helpful. If you have any questions or feedback, feel free to reach out. Happy coding! 🚀

Top comments (4)

Collapse
 
dainiuss profile image
Dainius Stepulevicius

I would not say there is something 'advanced' about these. It should be in every experienced Typescript dev toolkit. I have seen too many people 'knowing' Typescript when they put any as type for anything more complex that flat object or something can't be bothered to Google 'how to'

Collapse
 
diso profile image
Wayne Rockett

I enjoyed that read, it made me realise I probably only use Partial, Record and ReturnType, I should look at using the others on occasion. 👍🏼

Collapse
 
bhataasim profile image
Bhat Aasim

Thanks

Collapse
 
aditya_kumarbodapati_1ba profile image
Aditya Kumar Bodapati

It is great to see all at one place. Thank you @bhataasim .

But, i think the example use gave for NonNullable type is wrong. Can you check once?
Usage:

type Foo = string | number | null;
const foo: Foo = null // Valid

type NewFoo = NonNullable<Foo>;
const newFoo: NewFoo = null // Error: Type 'null' is not assignable to type 'NewFoo'
Enter fullscreen mode Exit fullscreen mode

Correct me if am wrong.