Reading Utility Types
Readonly, Pick, Omit, ReturnType, Parameters — reading TypeScript built-in utility types in plain English
TypeScript utility types vocabulary
- Readonly<T> — all properties become read-only at compile time; assignment errors on any property
- Pick<T, K> — subtype with only the listed keys; Omit<T, K> — subtype with all keys except listed
- ReturnType<typeof fn> — infers the return type; typeof converts a value to its type
- Parameters<typeof fn> — extracts parameter types as a tuple; use with rest spread to forward calls
- Awaited<T> — unwraps Promise<T> to T; useful with ReturnType for async functions
Question 0 of 5
What does Readonly<Config> prevent?interface Config {
apiUrl: string;
timeout: number;
retries: number;
}
const config: Readonly<Config> = {
apiUrl: "https://api.example.com",
timeout: 5000,
retries: 3,
};
config.apiUrl = "https://other.com"; // Error!
config.timeout = 10000; // Error!
- Readonly<T> =
{ readonly [P in keyof T]: T[P] }— adds thereadonlymodifier to every property - Compile-time only — the readonly constraint is checked by TypeScript; the JavaScript runtime does not enforce it (unlike Object.freeze)
- Shallow — only the top-level properties are readonly; nested objects can still be mutated
- Use cases — function parameters that should not be modified, configuration objects, action payloads
Object.freeze(). For TypeScript deep readonly, use the DeepReadonly<T> pattern from utility-types libraries.Read this usage of Pick. What type does it create?interface User {
id: number;
name: string;
email: string;
password: string;
role: "admin" | "user";
createdAt: Date;
}
type PublicUser = Pick<User, "id" | "name">;
function getPublicProfile(user: User): PublicUser {
return { id: user.id, name: user.name };
}
- Pick<User, "id" | "name"> =
{ id: number; name: string }— only the selected keys - Implementation:
type Pick<T, K extends keyof T> = { [P in K]: T[P] } - Use cases — API response types (hide sensitive fields), form state (only the fields you need), view models
- vs. Omit: Pick = "include these keys"; Omit = "exclude these keys"; use Pick when you want few keys, Omit when you want to exclude few keys
What is Omit<User, "password">?interface User {
id: number;
name: string;
email: string;
password: string;
role: "admin" | "user";
}
type SafeUser = Omit<User, "password">;
async function getUser(id: number): Promise<SafeUser> {
const user = await db.findUser(id);
const { password, ...rest } = user;
return rest;
}
- Omit<User, "password"> =
{ id: number; name: string; email: string; role: ... } - Implementation:
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>> - Multiple keys:
Omit<User, "password" | "createdAt"> - Use cases — exclude sensitive fields from API responses, exclude server-managed fields from input types, create "create" types from "read" types
type CreateUserInput = Omit<User, "id" | "createdAt"> — exclude auto-generated fields from the creation payload.What does ReturnType<typeof fetchUser> infer?async function fetchUser(id: number) {
const res = await fetch(`/api/users/${id}`);
return res.json() as Promise<{ id: number; name: string; email: string }>;
}
type FetchUserReturn = ReturnType<typeof fetchUser>;
// FetchUserReturn = Promise<{ id: number; name: string; email: string }>
- typeof fn — gets the type of the function itself (not its return value); required because ReturnType works on types, not values
- ReturnType<T> =
T extends (...args: any) => infer R ? R : never— uses conditional type inference - Awaited<ReturnType<...>> — unwraps Promise to get the resolved type:
{ id: number; name: string; email: string } - Use cases — derive types from third-party functions you don't control, keep types in sync with implementation changes
Read this usage of Parameters. What does it extract?function createUser(
name: string,
email: string,
role: "admin" | "user",
age?: number
): User { /* ... */ }
type CreateUserParams = Parameters<typeof createUser>;
// [name: string, email: string, role: "admin" | "user", age?: number]
function logAndCreate(...args: CreateUserParams): User {
console.log("Creating user:", args[0]);
return createUser(...args);
}
- Parameters<T> =
T extends (...args: infer P) => any ? P : never— infers the args tuple - Tuple type — an array type with a fixed number of elements and specific types for each position;
[string, number]is a 2-element tuple - Rest spread with tuple —
...args: CreateUserParamsletslogAndCreateaccept the exact same parameters ascreateUser - ConstructorParameters<T> — same but for class constructors