DEV Community

Abhimanyu jangid
Abhimanyu jangid

Posted on

Mastering TypeScript Utility Types with Real-World Examples

Mastering TypeScript Utility Types with Real-World Examples

💡 In this blog, we’ll focus on leveling up your TypeScript—not just covering the basics.
Many developers know TypeScript, but I’ve noticed most only scratch the surface. For instance, they create a User interface, and later, when updating that user, they rewrite a new interface instead of reusing or modifying the original. This bloats your code and is not scalable.
Let’s explore smarter ways using TypeScript’s built-in utility types.


Why Utility Types Matter

TypeScript offers several utility types that help you write DRY, clean, and type-safe code. These types are essential when working with large applications where data shapes often repeat, slightly modified, across components, services, and APIs.


1. Omit<Type, Keys>

Removes specific properties from a type.

📌 Use Case: Hiding sensitive data before sending to the client.

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

type SafeUser = Omit<User, "password">;

// 👇 SafeUser will be:
// {
//   id: string;
//   name: string;
//   email: string;
// }

Enter fullscreen mode Exit fullscreen mode

2. Pick<Type, Keys>

Creates a new type by selecting specific properties from an existing one.

📌 Use Case: Forms or query parameters that only need a subset of the object.

type LoginPayload = Pick<User, "email" | "password">;

// 👇 LoginPayload will be:
// {
//   email: string;
//   password: string;
// }

Enter fullscreen mode Exit fullscreen mode

3. Partial<Type>

Marks all properties of a type as optional.

📌 Use Case: PATCH or update APIs where fields are not required.

type UpdateUserPayload = Partial<User>;

// 👇 UpdateUserPayload will be:
// {
//   id?: string;
//   name?: string;
//   email?: string;
//   password?: string;
// }

Enter fullscreen mode Exit fullscreen mode

4. Required<Type>

Opposite of Partial, it makes all fields mandatory.

📌 Use Case: Enforcing complete data before saving to the DB.

interface UserInput {
  name?: string;
  email?: string;
}

type ValidUserInput = Required<UserInput>;

// 👇 ValidUserInput will be:
// {
//   name: string;
//   email: string;
// }

Enter fullscreen mode Exit fullscreen mode

5. Readonly<Type>

Makes all properties immutable (read-only).

📌 Use Case: Prevent accidental mutation of constants or config objects.

type AppConfig = {
  version: string;
  debug: boolean;
};

const config: Readonly<AppConfig> = {
  version: "1.0.0",
  debug: false,
};

config.version = "2.0.0"; // ❌ Error: Cannot assign to 'version' because it is a read-only property.

Enter fullscreen mode Exit fullscreen mode

6. Record<Keys, Type>

Creates an object type with keys of type Keys and values of type Type.

📌 Use Case: Status enums, language translations, or key-based mapping.

type Status = "pending" | "approved" | "rejected";

const statusLabels: Record<Status, string> = {
  pending: "Waiting for Review",
  approved: "Accepted",
  rejected: "Declined",
};

// 👇 statusLabels has:
// {
//   pending: string;
//   approved: string;
//   rejected: string;
// }

Enter fullscreen mode Exit fullscreen mode

7. Exclude<UnionType, ExcludedMembers>

Removes types from a union.

type Status = "draft" | "published" | "archived";

type VisibleStatus = Exclude<Status, "archived">;

//  👉 VisibleStatus = "draft" | "published"

Enter fullscreen mode Exit fullscreen mode

8. Extract<Type, Union>

Opposite of Exclude, it keeps only the matched members.

type Status = "draft" | "published" | "archived";

type LiveStatus = Extract<Status, "published" | "archived">;

//  👉 LiveStatus = "published" | "archived"


Enter fullscreen mode Exit fullscreen mode

9. NonNullable<Type>

Removes null and undefined from a type.

type Name = string | null | undefined;

type ValidName = NonNullable<Name>;

//  👉 ValidName = string

Enter fullscreen mode Exit fullscreen mode

Real-Life Example

Imagine you have a User interface and want to handle different parts of the app:

interface User {
  id: string;
  name: string;
  email: string;
  password: string;
}
Enter fullscreen mode Exit fullscreen mode
  • For signup payload: use Pick<User, "name" | "email" | "password">
  • For profile update: use Partial<Pick<User, "name" | "email">>
  • For API response: use Omit<User, "password">

This way, you reuse and reshape your base type efficiently—no code duplication!


Pro Tip

When your app grows, you’ll find yourself using combinations like:

type UpdatePayload = Partial<Omit<User, "id" | "password">>;
Enter fullscreen mode Exit fullscreen mode

And that’s perfectly fine—compose types like Lego blocks.


Conclusion

If you’re working on a large frontend/backend app with React, Express, or any TypeScript-based stack, mastering these utility types will make your code:

  • Cleaner
  • More maintainable
  • More DRY
  • Fully type-safe

Don’t just “use” TypeScript—leverage it.


Top comments (0)