TL;DR
Master TypeScript utility types like Partial, Pick, Omit, and Record. Transform existing types without repetition.
Key concepts
- TypeScript utility types
- Partial Pick Omit
- TypeScript Record type
- mapped types TypeScript
Utility Types
TypeScript provides built-in utility types that transform existing types. These let you create new types from old ones without repetition.
Partial and Required
Partial<T> makes all properties optional. Required<T> makes all properties required.
interface User {
id: number;
name: string;
email: string;
age: number;
}
// Partial — all fields optional (great for updates)
function updateUser(id: number, updates: Partial<User>): User {
const existing: User = { id, name: "Alice", email: "alice@example.com", age: 30 };
return { ...existing, ...updates };
}
const updated = updateUser(1, { name: "Alice Smith", age: 31 });
console.log(`Updated: ${updated.name}, age ${updated.age}`);
// Required — all fields required
interface Config {
host?: string;
port?: number;
debug?: boolean;
}
function startServer(config: Required<Config>): void {
console.log(`Server: ${config.host}:${config.port} (debug: ${config.debug})`);
}
startServer({ host: "localhost", port: 3000, debug: false });
Pick and Omit
Pick<T, K> creates a type with only selected properties. Omit<T, K> creates a type without specified properties.
interface Article {
id: number;
title: string;
body: string;
author: string;
publishedAt: string;
tags: string[];
}
// Pick — only the fields you need
type ArticlePreview = Pick<Article, "id" | "title" | "author">;
const preview: ArticlePreview = {
id: 1,
title: "TypeScript Utility Types",
author: "Alice"
};
console.log(`Preview: "${preview.title}" by ${preview.author}`);
// Omit — everything except specified fields
type CreateArticleInput = Omit<Article, "id" | "publishedAt">;
const input: CreateArticleInput = {
title: "New Article",
body: "Article content here...",
author: "Bob",
tags: ["typescript", "tutorial"]
};
console.log(`New article: "${input.title}" [${input.tags.join(", ")}]`);
// Combine them
type ArticleUpdate = Partial<Omit<Article, "id">>;
const update: ArticleUpdate = { title: "Updated Title" };
console.log(`Update: ${JSON.stringify(update)}`);
Record
Record<K, V> creates an object type with keys of type K and values of type V.
type Status = "pending" | "active" | "archived";
// Record creates a type with all Status keys
const statusLabels: Record<Status, string> = {
pending: "Awaiting Review",
active: "Currently Active",
archived: "No Longer Active"
};
const statusColors: Record<Status, string> = {
pending: "#FFA500",
active: "#00FF00",
archived: "#808080"
};
const statuses: Status[] = ["pending", "active", "archived"];
statuses.forEach(s => {
console.log(`${statusLabels[s]} (${statusColors[s]})`);
});
// Record with dynamic keys
type UserScores = Record<string, number>;
const scores: UserScores = {
alice: 95,
bob: 87,
charlie: 92
};
Object.entries(scores).forEach(([name, score]) => {
console.log(`${name}: ${score}`);
});
Exclude, Extract, and NonNullable
Work with union types by filtering members.
type AllTypes = string | number | boolean | null | undefined;
// Exclude — remove types from a union
type Primitives = Exclude<AllTypes, null | undefined>;
// Result: string | number | boolean
// Extract — keep only matching types
type NumOrStr = Extract<AllTypes, string | number>;
// Result: string | number
// NonNullable — remove null and undefined
type Defined = NonNullable<AllTypes>;
// Result: string | number | boolean
// Practical example
type ApiResult = "success" | "error" | "loading" | "idle";
type ActiveState = Exclude<ApiResult, "idle">;
function handleState(state: ActiveState): string {
switch (state) {
case "success": return "Data loaded!";
case "error": return "Something went wrong";
case "loading": return "Please wait...";
}
}
const states: ActiveState[] = ["loading", "success", "error"];
states.forEach(s => console.log(`${s}: ${handleState(s)}`));
ReturnType and Parameters
Extract types from function signatures.
function createUser(name: string, age: number, admin: boolean) {
return { id: Math.random(), name, age, admin, createdAt: new Date() };
}
// Extract the return type
type User = ReturnType<typeof createUser>;
// Extract parameter types
type CreateUserParams = Parameters<typeof createUser>;
const user: User = createUser("Alice", 30, true);
console.log(`User: ${user.name}, admin: ${user.admin}`);
// Use with existing functions
function formatDate(date: Date, locale: string): string {
return date.toLocaleDateString(locale);
}
type FormatDateReturn = ReturnType<typeof formatDate>; // string
type FormatDateParams = Parameters<typeof formatDate>; // [Date, string]
const args: FormatDateParams = [new Date(), "en-US"];
const result: FormatDateReturn = formatDate(...args);
console.log(`Formatted: ${result}`);
Try It Yourself
// Build a type-safe form builder
interface FormField {
label: string;
type: "text" | "email" | "number" | "select";
required: boolean;
options?: string[];
}
type FormSchema = Record<string, FormField>;
type FormValues<T extends FormSchema> = {
[K in keyof T]: T[K]["type"] extends "number" ? number : string;
};
type FormErrors<T extends FormSchema> = Partial<Record<keyof T, string>>;
const schema = {
name: { label: "Name", type: "text" as const, required: true },
email: { label: "Email", type: "email" as const, required: true },
age: { label: "Age", type: "number" as const, required: false },
role: { label: "Role", type: "select" as const, required: true, options: ["admin", "user"] },
} satisfies FormSchema;
type MyFormValues = FormValues<typeof schema>;
type MyFormErrors = FormErrors<typeof schema>;
const values: MyFormValues = { name: "Alice", email: "alice@co.com", age: 30, role: "admin" };
const errors: MyFormErrors = { email: "Invalid email format" };
console.log("Form values:", JSON.stringify(values));
console.log("Form errors:", JSON.stringify(errors));
// Validate
Object.entries(schema).forEach(([key, field]) => {
const value = values[key as keyof MyFormValues];
if (field.required && !value) {
console.log(`${field.label} is required`);
} else {
console.log(`${field.label}: ${value}`);
}
});
Key Takeaways
Partial<T>makes all properties optional — perfect for update functionsPick<T, K>andOmit<T, K>select or exclude specific propertiesRecord<K, V>creates object types with specific key and value typesExcludeandExtractfilter union type membersReturnTypeandParametersextract types from functions- Combine utility types for powerful transformations:
Partial<Omit<T, "id">>
Pro Tip: When you find yourself defining a type that's almost identical to an existing one, reach for utility types first.
Pick,Omit, andPartialeliminate redundancy and keep types in sync — when the source type changes, all derived types update automatically.
Next Steps
Utility types transform type shapes, but decorators transform runtime behavior. Next, you'll learn how to use decorators to add logging, validation, and other cross-cutting concerns to classes and methods.
Two-tier handoff: this document is the complete reading surface. Continue learning for stateful practice, progress, and real sandbox execution.