DEV Community

Cover image for Mastering TypeScript Utility Types
Sharif
Sharif

Posted on

Mastering TypeScript Utility Types

When working with TypeScript, one of the most powerful tools in our developer toolkit is utility types. These built-in helpers let us transform types, enforce constraints, and write more flexible, safer code.

In this blog, we’ll break down the most common TypeScript utility types with simple, real-world examples, making them easier to understand and apply in our own codebase.


Common TypeScript Utility Types

1. Partial<T>

Makes all properties in a type optional.

Use case: When updating only part of an object (e.g. a PATCH API).

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

const updateUser = (id: string, data: Partial<User>) => {
  // data can contain name, email, or both (or neither)
};

updateUser("1", { email: "new@example.com" });
Enter fullscreen mode Exit fullscreen mode

2. Required<T>

Makes all properties in a type required, even if they were optional before.

type User = {
  name?: string;
  email?: string;
};

const createUser = (user: Required<User>) => {
  // name and email must be provided
};

createUser({ name: "John", email: "john@example.com" });
Enter fullscreen mode Exit fullscreen mode

3. Readonly<T>

Makes all properties read-only — they can’t be changed after being set.

type User = {
  name: string;
};

const user: Readonly<User> = { name: "Alice" };

// user.name = "Bob"; ❌ Error: Cannot assign to 'name'
Enter fullscreen mode Exit fullscreen mode

4. Record<K, T>

Creates an object type with keys of type K and values of type T.

Use case: Creating a mapping object.

type Role = "admin" | "user" | "guest";

const permissions: Record<Role, string[]> = {
  admin: ["create", "read", "update", "delete"],
  user: ["read"],
  guest: [],
};
Enter fullscreen mode Exit fullscreen mode

5. Pick<T, K>

Creates a new type by picking a subset of properties from T.

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

type PublicUser = Pick<User, "id" | "name">;

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

6. Omit<T, K>

Creates a type by excluding one or more properties from T.

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

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

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

7. Exclude<T, U>

Removes from T the types that are assignable to U.

type Roles = "admin" | "user" | "guest";
type LimitedRoles = Exclude<Roles, "guest">;

// "admin" | "user"
Enter fullscreen mode Exit fullscreen mode

8. Extract<T, U>

Keeps from T only the types that are assignable to U.

type Roles = "admin" | "user" | "guest";
type StaffRoles = Extract<Roles, "admin" | "user">;

// "admin" | "user"
Enter fullscreen mode Exit fullscreen mode

9. NonNullable<T>

Removes null and undefined from a type.

type MaybeUser = string | null | undefined;
type User = NonNullable<MaybeUser>; // string
Enter fullscreen mode Exit fullscreen mode

10. ReturnType<T>

Extracts the return type of a function.

const getUser = () => {
  return { id: 1, name: "Alice" };
};

type User = ReturnType<typeof getUser>; // { id: number; name: string }
Enter fullscreen mode Exit fullscreen mode

11. Parameters<T>

Extracts the parameter types of a function into a tuple.

function greet(name: string, age: number) {
  return `Hello ${name}, age ${age}`;
}

type GreetParams = Parameters<typeof greet>; // [string, number]
Enter fullscreen mode Exit fullscreen mode

12. ConstructorParameters<T>

Extracts the types of a class constructor's parameters.

class Person {
  constructor(public name: string, public age: number) {}
}

type PersonArgs = ConstructorParameters<typeof Person>; // [string, number]
Enter fullscreen mode Exit fullscreen mode

13. InstanceType<T>

Gets the instance type of a class constructor.

class Car {
  wheels = 4;
}

type CarInstance = InstanceType<typeof Car>; // Car
Enter fullscreen mode Exit fullscreen mode

14. Awaited<T>

Unwraps the type of a Promise, recursively if needed.

async function fetchUser(): Promise<{ name: string }> {
  return { name: "Alice" };
}

type User = Awaited<ReturnType<typeof fetchUser>>; // { name: string }
Enter fullscreen mode Exit fullscreen mode

15. NoInfer<T>

Prevents TypeScript from inferring a type too early. Useful in generic helpers. It is not built-in but often used via workaround.

type NoInfer<T> = [T][T extends any ? 0 : never];

function useDefault<T>(value: T, fallback: NoInfer<T>): T {
  return value !== undefined ? value : fallback;
}
Enter fullscreen mode Exit fullscreen mode

Wrapping Up

TypeScript’s utility types are secret weapon to write clean, reusable, and safe type definitions without unnecessary duplication. By using them, we can simplify code and boost our productivity.


References

Top comments (0)