Utility types
In this chapter, you learn about the utility types built into TypeScript.
What you learn in this chapter
- How to use
Partial<T>andRequired<T> - Creating read-only types with
Readonly<T> - Selecting and excluding properties with
Pick<T, K>andOmit<T, K> - Creating an object type with
Record<K, T> - Operating on union types with
Exclude<T, U>andExtract<T, U> - Excluding null/undefined with
NonNullable<T> - Getting a function's types with
ReturnType<T>andParameters<T>
By combining utility types, you can derive new types from existing ones, which makes code easier to reuse.
Partial<T>
Partial<T> makes all properties optional (omittable).
interface User {
id: number;
name: string;
email: string;
age: number;
}
// All properties become optional
type PartialUser = Partial<User>;
// Result:
// {
// id?: number;
// name?: string;
// email?: string;
// age?: number;
// }
A practical example: an update function
// An update function using Partial<User>
// You can pass only the properties you want to update
function updateUser(user: User, updates: Partial<User>): User {
// Apply the updates to the original object with spread syntax
return { ...user, ...updates };
}
const user: User = {
id: 1,
name: "Yamada Taro",
email: "yamada@example.com",
age: 30
};
// Update only age (the other properties can be omitted)
const updated = updateUser(user, { age: 31 });
console.log(updated);
// { id: 1, name: "Yamada Taro", email: "yamada@example.com", age: 31 }
// Update multiple properties
const updated2 = updateUser(user, { name: "Yamada Jiro", age: 25 });
console.log(updated2);
// { id: 1, name: "Yamada Jiro", email: "yamada@example.com", age: 25 }
Required<T>
Required<T> makes all properties required (the opposite of Partial).
interface PartialConfig {
apiUrl?: string;
timeout?: number;
retryCount?: number;
}
// All properties become required
type FullConfig = Required<PartialConfig>;
// Result:
// {
// apiUrl: string;
// timeout: number;
// retryCount: number;
// }
const config: FullConfig = {
apiUrl: "https://api.example.com",
timeout: 5000,
retryCount: 3
// all are required, so missing even one is an error
};
A practical example: validating configuration
// Receive optional input and fill in default values
function createConfig(input: PartialConfig): Required<PartialConfig> {
return {
apiUrl: input.apiUrl ?? "https://api.default.com",
timeout: input.timeout ?? 3000,
retryCount: input.retryCount ?? 3
};
}
// Specify only some
const config1 = createConfig({ apiUrl: "https://api.custom.com" });
console.log(config1);
// { apiUrl: "https://api.custom.com", timeout: 3000, retryCount: 3 }
Readonly<T>
Readonly<T> makes all properties read-only.
interface Config {
apiUrl: string;
timeout: number;
retryCount: number;
}
// All properties become read-only
type ReadonlyConfig = Readonly<Config>;
// Result:
// {
// readonly apiUrl: string;
// readonly timeout: number;
// readonly retryCount: number;
// }
const config: ReadonlyConfig = {
apiUrl: "https://api.example.com",
timeout: 5000,
retryCount: 3
};
// Trying to change it is an error
// config.timeout = 10000;
// Error: Cannot assign to 'timeout' because it is a read-only property.
A practical example: immutable state management
interface AppState {
user: { name: string; loggedIn: boolean };
theme: "light" | "dark";
notifications: string[];
}
// Treat the state as read-only
function processState(state: Readonly<AppState>): void {
console.log(`User: ${state.user.name}`);
console.log(`Theme: ${state.theme}`);
// Trying to change state is an error
// state.theme = "dark"; // Error
}
const initialState: AppState = {
user: { name: "Yamada Taro", loggedIn: true },
theme: "light",
notifications: []
};
processState(initialState);
Pick<T, K>
Pick<T, K> extracts only the specified properties K from type T.
interface User {
id: number;
name: string;
email: string;
age: number;
address: string;
}
// A type with only name and email
type UserContact = Pick<User, "name" | "email">;
// Result:
// {
// name: string;
// email: string;
// }
const contact: UserContact = {
name: "Yamada Taro",
email: "yamada@example.com"
// id and age are not needed (they cannot be specified)
};
A practical example: limiting a function's arguments
// Receive only the information needed at login
function login(credentials: Pick<User, "email">): void {
console.log(`Logging in: ${credentials.email}`);
}
// Receive only the information needed for display
function displayUserCard(user: Pick<User, "name" | "age">): void {
console.log(`${user.name} (${user.age})`);
}
const fullUser: User = {
id: 1,
name: "Yamada Taro",
email: "yamada@example.com",
age: 30,
address: "Tokyo"
};
login(fullUser); // OK
displayUserCard(fullUser); // OK
Omit<T, K>
Omit<T, K> excludes the specified properties K from type T (the opposite of Pick).
interface User {
id: number;
name: string;
email: string;
password: string;
createdAt: Date;
}
// A type with password excluded
type PublicUser = Omit<User, "password">;
// Result:
// {
// id: number;
// name: string;
// email: string;
// createdAt: Date;
// }
// Exclude multiple properties
type UserWithoutMeta = Omit<User, "id" | "createdAt">;
// Result:
// {
// name: string;
// email: string;
// password: string;
// }
A practical example: excluding sensitive information in an API response
// Hold the full data internally
const internalUser: User = {
id: 1,
name: "Yamada Taro",
email: "yamada@example.com",
password: "hashedPassword123",
createdAt: new Date()
};
// A function that excludes sensitive information for an API response
function toPublicUser(user: User): PublicUser {
// Exclude password with destructuring and return the rest
const { password, ...publicInfo } = user;
return publicInfo;
}
const publicUser = toPublicUser(internalUser);
console.log(publicUser);
// { id: 1, name: "Yamada Taro", email: "yamada@example.com", createdAt: ... }
// password is not included
Record<K, T>
Record<K, T> creates an object type by specifying the key type K and the value type T.
// An object with string keys and number values
type StringNumberMap = Record<string, number>;
const scores: StringNumberMap = {
math: 90,
english: 85,
science: 92
};
A practical example: restricting to specific keys
// Restrict the grades as keys
type Grade = "A" | "B" | "C" | "D" | "F";
type GradeCount = Record<Grade, number>;
const gradeDistribution: GradeCount = {
A: 10,
B: 20,
C: 30,
D: 15,
F: 5
// all grades are required
};
// Descriptions of HTTP status codes
type HttpStatus = 200 | 404 | 500;
type StatusMessages = Record<HttpStatus, string>;
const messages: StatusMessages = {
200: "Success",
404: "Not found",
500: "Server error"
};
A practical example: dynamic mapping
// A user map keyed by user ID
interface User {
id: number;
name: string;
}
type UserMap = Record<number, User>;
const users: UserMap = {
1: { id: 1, name: "Yamada Taro" },
2: { id: 2, name: "Tanaka Hanako" },
3: { id: 3, name: "Suzuki Jiro" }
};
// Get a user by ID
function getUser(id: number): User | undefined {
return users[id];
}
Exclude<T, U>
Exclude<T, U> excludes from union type T the members that match type U.
type AllStatus = "pending" | "approved" | "rejected" | "cancelled";
// Exclude "cancelled"
type ActiveStatus = Exclude<AllStatus, "cancelled">;
// Result: "pending" | "approved" | "rejected"
// Exclude multiple types
type OngoingStatus = Exclude<AllStatus, "approved" | "rejected">;
// Result: "pending" | "cancelled"
A practical example: filtering by condition
// All types
type AllTypes = string | number | boolean | null | undefined;
// Exclude null/undefined
type PrimitiveTypes = Exclude<AllTypes, null | undefined>;
// Result: string | number | boolean
// Exclude everything except string
type NonStringTypes = Exclude<AllTypes, string>;
// Result: number | boolean | null | undefined
Extract<T, U>
Extract<T, U> extracts from union type T only the members that match type U (the opposite of Exclude).
type AllTypes = string | number | boolean | null | undefined;
// Extract only string and number
type StringOrNumber = Extract<AllTypes, string | number>;
// Result: string | number
// Extract only boolean
type BooleanOnly = Extract<AllTypes, boolean>;
// Result: boolean
A practical example: extracting common types
type AdminPermissions = "read" | "write" | "delete" | "admin";
type UserPermissions = "read" | "write" | "comment";
// Extract the permissions common to both
type CommonPermissions = Extract<AdminPermissions, UserPermissions>;
// Result: "read" | "write"
NonNullable<T>
NonNullable<T> excludes null and undefined from type T.
type MaybeNumber = number | null | undefined;
type DefiniteNumber = NonNullable<MaybeNumber>;
// Result: number
type MaybeString = string | null;
type DefiniteString = NonNullable<MaybeString>;
// Result: string
A practical example: guaranteeing a value exists
function processValue(value: number | null | undefined): void {
if (value !== null && value !== undefined) {
// Here, value is the number type
const definite: NonNullable<typeof value> = value;
console.log(definite * 2);
}
}
// Exclude null/undefined from an array
function filterNullish<T>(arr: (T | null | undefined)[]): NonNullable<T>[] {
return arr.filter((item): item is NonNullable<T> => item != null);
}
const mixedArray = [1, null, 2, undefined, 3];
const cleanArray = filterNullish(mixedArray);
console.log(cleanArray); // [1, 2, 3]
ReturnType<T>
ReturnType<T> gets the return type of a function type T.
// Define a function
function createUser() {
return {
id: 1,
name: "Yamada Taro",
email: "yamada@example.com"
};
}
// Get the return type of the createUser function
// Get the function's type with typeof, then extract the return type with ReturnType
type User = ReturnType<typeof createUser>;
// Result:
// {
// id: number;
// name: string;
// email: string;
// }
// Use the obtained type
const user: User = {
id: 2,
name: "Tanaka Hanako",
email: "tanaka@example.com"
};
A practical example: deriving a type from an existing function
// A function from a third-party library
function fetchUserData(id: number) {
return {
user: { id, name: "User", email: "user@example.com" },
metadata: { fetchedAt: new Date(), source: "api" }
};
}
// Reuse the function's return type
type FetchResult = ReturnType<typeof fetchUserData>;
// Store it in a cache
const cache: Map<number, FetchResult> = new Map();
function getCachedOrFetch(id: number): FetchResult {
if (cache.has(id)) {
return cache.get(id)!;
}
const result = fetchUserData(id);
cache.set(id, result);
return result;
}
Parameters<T>
Parameters<T> gets the parameter types of a function type T as a tuple.
function updateUser(id: number, name: string, email: string): void {
console.log(`Updating user ${id}: ${name}, ${email}`);
}
// Get the parameter types as a tuple
type UpdateUserParams = Parameters<typeof updateUser>;
// Result: [number, string, string]
// Call the function with the spread operator
const params: UpdateUserParams = [1, "Yamada Taro", "yamada@example.com"];
updateUser(...params);
A practical example: a function wrapper
function originalFunction(a: number, b: string, c: boolean): string {
return `${a}-${b}-${c}`;
}
// A wrapper function that receives the same arguments as the original
function wrappedFunction(...args: Parameters<typeof originalFunction>): string {
console.log("Before:", args);
const result = originalFunction(...args);
console.log("After:", result);
return result;
}
wrappedFunction(42, "hello", true);
// Before: [42, "hello", true]
// After: 42-hello-true
ConstructorParameters<T>
ConstructorParameters<T> gets the parameter types of a constructor function (class) as a tuple.
class User {
constructor(
public id: number,
public name: string,
public email: string,
) {}
}
// Get the constructor's parameter types as a tuple
type UserArgs = ConstructorParameters<typeof User>;
// Result: [number, string, string]
// Useful in places like factory functions, where you want to create an instance with the same arguments
function createUser(...args: ConstructorParameters<typeof User>): User {
console.log('Creating user with args:', args)
return new User(...args)
}
const user = createUser(1, 'Yamada Taro', 'yamada@example.com')
Awaited<T> (TS 4.5+)
Awaited<T> recursively takes out the type of a value wrapped in a Promise. Use it when you want the type of the result resolved with await.
type A = Awaited<Promise<string>>; // string
type B = Awaited<Promise<Promise<number>>>; // number (resolved recursively)
type C = Awaited<string>; // string (if not a Promise, the type itself)
A practical example: getting the return type of an async function
async function fetchUser() {
const response = await fetch('/api/users/1')
return (await response.json()) as { id: number; name: string }
}
// ReturnType cannot strip the Promise, so combine it with Awaited
type User = Awaited<ReturnType<typeof fetchUser>>;
// Result: { id: number; name: string }
NoInfer<T> (TS 5.4+)
NoInfer<T> is a utility that excludes a specific argument from the inference of that type parameter. Use it when the same type parameter is used in multiple places and you want to explicitly limit the basis of inference (for example, in createStreetLight(colors, defaultColor), to keep defaultColor from influencing the inference of C). It lets you express the intent of "I do not want the type inferred from here" through the type, and it is useful in library API design. For a detailed explanation of the problem and solution, with code examples, see the NoInfer section of the previous chapter, Generic constraints.
String manipulation types (TS 4.1+)
These are built-in string manipulation types that can be used in combination with Template Literal Types.
| Type | Behavior | Example |
|---|---|---|
Uppercase<S> | Uppercase | Uppercase<'hello'> → 'HELLO' |
Lowercase<S> | Lowercase | Lowercase<'WORLD'> → 'world' |
Capitalize<S> | Capitalize the first letter | Capitalize<'user'> → 'User' |
Uncapitalize<S> | Lowercase the first letter | Uncapitalize<'User'> → 'user' |
type EventName<S extends string> = `on${Capitalize<S>}`;
type ClickEvent = EventName<'click'>; // 'onClick'
type SubmitEvent = EventName<'submit'>; // 'onSubmit'
// Auto-generating getter names
type Getter<K extends string> = `get${Capitalize<K>}`;
type UserGetters = Getter<'name' | 'email'>;
// Result: 'getName' | 'getEmail'
Since these string manipulation types were introduced in TypeScript 4.1, they have been the foundation for building type-safe DSLs in combination with Template Literal Types. Template Literal Types are covered in detail in Chapter 16 (Mapped Types and Conditional Types).
Combining utility types
By combining multiple utility types, you can create more complex types.
interface User {
id: number;
name: string;
email: string;
password: string;
role: "admin" | "user";
createdAt: Date;
updatedAt: Date;
}
// 1. A public type with sensitive information excluded
type PublicUser = Omit<User, "password">;
// 2. A type with only login information
type LoginCredentials = Pick<User, "email" | "password">;
// 3. An update type (everything except id, createdAt, updatedAt is optional)
type UserUpdateInput = Partial<Omit<User, "id" | "createdAt" | "updatedAt">>;
// 4. A read-only public user type
type ReadonlyPublicUser = Readonly<PublicUser>;
// Usage
const updateData: UserUpdateInput = {
name: "Yamada Jiro"
// everything is optional, so specify only the properties you want to change
};
const publicUser: ReadonlyPublicUser = {
id: 1,
name: "Yamada Taro",
email: "yamada@example.com",
role: "user",
createdAt: new Date(),
updatedAt: new Date()
};
// publicUser.name = "Changed"; // Error: read-only
Try it: design the type of an update function ★★
Implement types and functions that meet the following requirements.
Requirements:
- Define a
Producttype (id, name, price, category, inStock) - Implement an
updateProductfunction (you can pass only the properties you want to update) - Implement a
getProductSummaryfunction (returns only name and price) - Define a
ProductMaptype (a map keyed by category name to an array of products)
// Implement the following
// interface Product { ... }
// function updateProduct(...): Product
// function getProductSummary(...): ...
// type ProductMap = ...
Hint
- Define the update argument with
Partial<T> - Extract only the necessary properties with
Pick<T, K> - Define the category map with
Record<K, T> - Apply the update with spread syntax
Answer and explanation
// Define the product type
interface Product {
id: number;
name: string;
price: number;
category: string;
inStock: boolean;
}
// Update function: make only some properties updatable with Partial<Product>
// However, make id non-updatable by excluding it with Omit
function updateProduct(
product: Product,
updates: Partial<Omit<Product, "id">>
): Product {
return { ...product, ...updates };
}
// Get a summary: return only the necessary properties with Pick<Product, ...>
type ProductSummary = Pick<Product, "name" | "price">;
function getProductSummary(product: Product): ProductSummary {
return {
name: product.name,
price: product.price
};
}
// Category map: an array of products per category with Record<string, Product[]>
type ProductMap = Record<string, Product[]>;
// Test
const laptop: Product = {
id: 1,
name: "Laptop",
price: 120000,
category: "electronics",
inStock: true
};
// Update the price and stock
const updatedLaptop = updateProduct(laptop, { price: 110000, inStock: false });
console.log(updatedLaptop);
// { id: 1, name: "Laptop", price: 110000, category: "electronics", inStock: false }
// Get a summary
const summary = getProductSummary(laptop);
console.log(summary);
// { name: "Laptop", price: 120000 }
// Create a category map
const productsByCategory: ProductMap = {
electronics: [laptop],
books: [
{ id: 2, name: "Introduction to TypeScript", price: 3000, category: "books", inStock: true }
]
};
console.log(productsByCategory.electronics);
// [{ id: 1, name: "Laptop", ... }]
Explanation:
Partial<Omit<Product, "id">>: exclude id, then make the rest optionalPick<Product, "name" | "price">: a type with only the necessary properties extractedRecord<string, Product[]>: a map type from string keys to arrays of products
By combining these utility types, you can design type-safe and flexible APIs.
Summary
What you learned in this chapter:
Partial<T>: make all properties optionalRequired<T>: make all properties requiredReadonly<T>: make all properties read-onlyPick<T, K>: extract only the specified propertiesOmit<T, K>: exclude the specified propertiesRecord<K, T>: create an object type by specifying the key and value typesExclude<T, U>: exclude a specific type from a union typeExtract<T, U>: extract a specific type from a union typeNonNullable<T>: exclude null/undefinedReturnType<T>: get a function's return typeParameters<T>: get a function's parameter types
In the next chapter, you will learn about Mapped Types and Conditional Types, and how to create your own utility types.
Common pitfalls for beginners
Common mistakes
❌ Forgetting that the result of Partial<T> includes undefined for everything
interface User {
name: string;
age: number;
}
type PartialUser = Partial<User>;
// { name?: string; age?: number; }
function processUser(user: PartialUser): void {
// ❌ user.name is string | undefined, so it cannot be used directly
// console.log(user.name.toUpperCase()); // Error
// ✅ An undefined check is needed
if (user.name !== undefined) {
console.log(user.name.toUpperCase());
}
}
Cause: Partial<T> makes all properties optional, so the values include undefined.
Solution: Do an undefined check before accessing a property.
❌ Not knowing that Readonly is shallow
interface User {
name: string;
address: {
city: string;
};
}
const user: Readonly<User> = {
name: "Taro",
address: { city: "Tokyo" }
};
// user.name = "Jiro"; // Error: read-only
// ❌ A nested object can still be changed!
user.address.city = "Osaka"; // OK (no error)
Cause: Readonly<T> is shallow read-only and does not apply to nested objects.
Solution: When you need deep read-only, create a custom recursive type or use a library.
❌ Not noticing a typo in the second argument of Pick or Omit
interface User {
id: number;
name: string;
email: string;
}
// ❌ TypeScript may not detect a typo (a non-existent key may not be an error)
// It has improved in TypeScript 4.x and later, but be careful
// ✅ Be type-safe with keyof
type UserKeys = keyof User; // "id" | "name" | "email"
type PickedUser = Pick<User, "name" | "email">; // OK
Cause: Depending on the TypeScript version, specifying a non-existent key may not produce an error.
Solution: Check the valid keys with keyof while using them.
❌ Passing the function itself to ReturnType
function createUser() {
return { id: 1, name: "Taro" };
}
// ❌ Passing the function itself is an error
// type UserType = ReturnType<createUser>; // Error
// ✅ Get the function's "type" with typeof, then pass it
type UserType = ReturnType<typeof createUser>;
// { id: number; name: string; }
Cause: ReturnType receives a "function type", but createUser is a value.
Solution: Get the function's type with typeof, then pass it to ReturnType.
❌ Trying to use Exclude on an object type
interface User {
id: number;
name: string;
password: string;
}
// ❌ Exclude is for union types; it cannot exclude object properties
// type PublicUser = Exclude<User, "password">; // does not work as intended
// ✅ Use Omit to exclude object properties
type PublicUser = Omit<User, "password">;
// { id: number; name: string; }
// The correct use of Exclude (exclude from a union type)
type AllStatus = "pending" | "approved" | "rejected";
type ActiveStatus = Exclude<AllStatus, "rejected">;
// "pending" | "approved"
Cause: Exclude excludes a specific type from a union type, and cannot be used to exclude object properties.
Solution: Use Omit to exclude object properties, and Exclude to exclude from a union type.