Utility Types in TypeScript
TypeScript provides a set of utility types that help you transform and manipulate existing types. These utility types are built into the TypeScript compiler and can dramatically improve your type definitions. In this tutorial, you’ll learn the most commonly used utility types and how to apply them in real-world scenarios.
Partial and Required
The Partial<T> utility type constructs a type with all properties of T set to optional. This is useful when you want to update an object partially.
interface User {
id: number;
name: string;
email: string;
age: number;
}
// All properties are now optional
type PartialUser = Partial<User>;
// This is valid - we can provide only some properties
function updateUser(id: number, updates: PartialUser): void {
console.log(`Updating user ${id}`, updates);
}
updateUser(1, { name: "Alice" }); // Only updating name
updateUser(1, { email: "alice@example.com", age: 30 }); // Multiple updates
Conversely, Required<T> makes all properties required, the opposite of Partial.
interface Config {
host?: string;
port?: number;
ssl?: boolean;
}
const fullConfig: Required<Config> = {
host: "localhost",
port: 8080,
ssl: true,
};
Pick and Omit
Pick<T, K> creates a new type by selecting specific properties K from type T.
interface Article {
id: number;
title: string;
content: string;
author: string;
publishedAt: Date;
status: "draft" | "published" | "archived";
}
// Only pick the fields we need for a preview
type ArticlePreview = Pick<Article, "id" | "title" | "author">;
const preview: ArticlePreview = {
id: 1,
title: "Understanding TypeScript Utility Types",
author: "John Doe",
};
Omit<T, K> does the opposite—it creates a type by excluding specific properties K from T.
type ArticleWithoutContent = Omit<Article, "content">;
const articleSummary: ArticleWithoutContent = {
id: 1,
title: "Understanding TypeScript Utility Types",
author: "John Doe",
publishedAt: new Date(),
status: "published",
};
Record
The Record<K, T> utility type constructs an object type whose property keys are K and values are T. It’s perfect for creating dictionaries or mapped types.
type Role = "admin" | "user" | "guest";
interface Permission {
canRead: boolean;
canWrite: boolean;
canDelete: boolean;
}
const rolePermissions: Record<Role, Permission> = {
admin: { canRead: true, canWrite: true, canDelete: true },
user: { canRead: true, canWrite: true, canDelete: false },
guest: { canRead: true, canWrite: false, canDelete: false },
};
function checkPermission(role: Role, action: keyof Permission): boolean {
return rolePermissions[role][action];
}
console.log(checkPermission("admin", "canDelete")); // true
console.log(checkPermission("guest", "canWrite")); // false
Extract and Exclude
Extract<T, U> extracts types from T that are assignable to U.
type T = Extract<"a" | "b" | "c" | "d", "a" | "c" | "f">;
// Result: "a" | "c"
type Numbers = Extract<string | number | boolean, number>;
// Result: number
// Practical example: extracting certain event types
type Event =
| { type: "click"; x: number; y: number }
| { type: "keydown"; key: string }
| { type: "focus" }
| { type: "blur" };
type MouseEvent = Extract<Event, { type: "click" }>;
// Result: { type: "click"; x: number; y: number }
Exclude<T, U> does the opposite—it removes types from T that are assignable to U.
type T = Exclude<"a" | "b" | "c" | "d", "a" | "c">;
// Result: "b" | "d"
type NotNull = Exclude<string | number | null | undefined, null | undefined>;
// Result: string | number
ReturnType and ParameterType
ReturnType<T> extracts the return type of a function type T.
function getUser() {
return { id: 1, name: "Alice" };
}
type UserReturn = ReturnType<typeof getUser>;
// Result: { id: number; name: string }
function getUsers() {
return [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }];
}
type UserList = ReturnType<typeof getUsers>;
// Result: { id: number; name: string }[]
ParameterType<T> (or Parameters<T>) extracts the parameter types of a function.
function createUser(name: string, age: number, email: string) {
return { name, age, email };
}
type CreateUserParams = Parameters<typeof createUser>;
// Result: [string, number, string]
const params: CreateUserParams = ["Alice", 30, "alice@example.com"];
const user = createUser(...params);
Practical Example: API Response Handler
Here’s how you can combine utility types in a real-world scenario:
interface ApiResponse {
id: number;
title: string;
body: string;
userId: number;
createdAt: string;
updatedAt: string;
}
// We want a form that can partially update an article
type UpdateArticleForm = Partial<Pick<ApiResponse, "title" | "body">>;
// We want to display article summaries in a list
type ArticleSummary = Pick<ApiResponse, "id" | "title" | "createdAt">;
// We want to validate incoming API data
function validateArticle(data: unknown): data is Partial<ApiResponse> {
if (typeof data !== "object" || data === null) return false;
const obj = data as Record<string, unknown>;
return (
obj.title === undefined || typeof obj.title === "string"
) && (
obj.body === undefined || typeof obj.body === "string"
);
}
Summary
TypeScript’s utility types are powerful tools that let you transform and manipulate types with ease:
- Partial/Required — Toggle property optionality
- Pick/Omit — Select or exclude specific properties
- Record — Create object types with specific keys and value types
- Extract/Exclude — Filter union types
- ReturnType/Parameters — Extract function type information
These utilities reduce boilerplate and make your type definitions more expressive and maintainable. In the next tutorial, we’ll explore decorators in TypeScript.