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 aUser
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;
// }
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;
// }
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;
// }
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;
// }
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.
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;
// }
7. Exclude<UnionType, ExcludedMembers>
Removes types from a union.
type Status = "draft" | "published" | "archived";
type VisibleStatus = Exclude<Status, "archived">;
// 👉 VisibleStatus = "draft" | "published"
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"
9. NonNullable<Type>
Removes null
and undefined
from a type.
type Name = string | null | undefined;
type ValidName = NonNullable<Name>;
// 👉 ValidName = string
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;
}
- 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">>;
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)