4. Utility Types
Built-in generic types that transform other types. Know these ten cold — they come up constantly.
Quick Reference
| Utility | Does |
|---|---|
Partial<T> | All properties optional |
Required<T> | All properties required |
Readonly<T> | All properties readonly |
Pick<T, K> | Keep only keys K |
Omit<T, K> | Remove keys K |
Record<K, V> | Object with keys K and values V |
Exclude<T, U> | Remove U from union T |
Extract<T, U> | Keep only U from union T |
NonNullable<T> | Remove null and undefined |
ReturnType<F> | Return type of a function |
Parameters<F> | Parameter types as a tuple |
Awaited<T> | Unwrap a promise |
1. Partial<T>
interface User {
id: number;
name: string;
email: string;
}
type UserUpdate = Partial<User>;
// { id?: number; name?: string; email?: string }
function updateUser(id: number, changes: Partial<User>) {
return db.user.update({ where: { id }, data: changes });
}
updateUser(1, { name: "Rahul" }); // only the fields you're changingThe canonical PATCH endpoint type.
2. Required<T>
interface Config {
host?: string;
port?: number;
}
function normalise(config: Config): Required<Config> {
return { host: config.host ?? "localhost", port: config.port ?? 3000 };
}Useful for "options in, resolved defaults out".
3. Readonly<T>
const config: Readonly<Config> = { host: "x", port: 80 };
// config.port = 90; // ErrorShallow only — nested objects stay mutable. as const is usually better for literals.
4. Pick<T, K> and Omit<T, K>
interface User {
id: number;
name: string;
email: string;
passwordHash: string;
createdAt: Date;
}
// Keep specific keys
type UserPreview = Pick<User, "id" | "name">;
// Remove specific keys — the safe API response type
type PublicUser = Omit<User, "passwordHash">;
// The create DTO
type CreateUser = Omit<User, "id" | "createdAt">;Interview Point
Prefer Omit over Pick for "everything except the secret" types. If someone later adds twoFactorSecret to User, Omit<User, "passwordHash"> automatically includes it in the response — a leak. Pick is fail-safe: new fields are excluded by default.
So the honest answer is: Pick for API responses (allowlist), Omit for convenience internally. Naming that trade-off scores well.
Omit Does Not Check Keys
type T = Omit<User, "nonExistentKey">; // no error — just returns UserA known wart. Pick does error on a bad key.
5. Record<K, V>
type Role = "admin" | "editor" | "viewer";
const permissions: Record<Role, string[]> = {
admin: ["read", "write", "delete"],
editor: ["read", "write"],
viewer: ["read"],
};Miss a role and it fails to compile — an exhaustive lookup table.
// Generic dictionary
type Cache = Record<string, User>;
// Nested
type Matrix = Record<string, Record<string, number>>;6. Exclude<T, U> and Extract<T, U>
Operate on unions, not object properties.
type Status = "idle" | "loading" | "success" | "error";
type Settled = Exclude<Status, "idle" | "loading">; // "success" | "error"
type Pending = Extract<Status, "idle" | "loading">; // "idle" | "loading"
type Primitive = Extract<string | number | object, string | number>;
// string | number7. NonNullable<T>
type MaybeUser = User | null | undefined;
type DefiniteUser = NonNullable<MaybeUser>; // User8. ReturnType<F>, Parameters<F>, Awaited<T>
function createUser(name: string, age: number) {
return { id: 1, name, age, createdAt: new Date() };
}
type User = ReturnType<typeof createUser>;
// { id: number; name: string; age: number; createdAt: Date }
type Args = Parameters<typeof createUser>; // [name: string, age: number]
async function fetchUser() {
return { id: 1, name: "Dipak" };
}
type FetchedUser = Awaited<ReturnType<typeof fetchUser>>;
// { id: number; name: string } — promise unwrappedWhy This Matters
Deriving a type from a function means it can never drift from the implementation. Very common with API clients and Zod schemas.
// Prisma / Drizzle pattern
type UserWithPosts = Awaited<ReturnType<typeof getUserWithPosts>>;Note the typeof — you need the type of the function value, not the function's return.
9. Parameters in Practice
function logged<F extends (...args: any[]) => any>(fn: F) {
return (...args: Parameters<F>): ReturnType<F> => {
console.log(`Calling ${fn.name}`, args);
return fn(...args);
};
}
const safeCreate = logged(createUser);
safeCreate("Dipak", 25); // fully typedA typed wrapper/decorator with no loss of signature.
10. String Manipulation Types
type Upper = Uppercase<"hello">; // "HELLO"
type Lower = Lowercase<"HELLO">; // "hello"
type Cap = Capitalize<"hello">; // "Hello"
type Uncap = Uncapitalize<"Hello">; // "hello"Combined with template literal types:
type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickEvent = EventName<"click">; // "onClick"
type CssVar<T extends string> = `--${T}`;
type ThemeVar = CssVar<"primary" | "secondary">;
// "--primary" | "--secondary"11. Composing Utilities
Real code combines them.
interface User {
id: number;
name: string;
email: string;
passwordHash: string;
createdAt: Date;
updatedAt: Date;
}
// Safe to send to a client
type PublicUser = Omit<User, "passwordHash">;
// POST body
type CreateUserDto = Omit<User, "id" | "createdAt" | "updatedAt">;
// PATCH body — everything optional except we need the id
type UpdateUserDto = Partial<CreateUserDto> & Pick<User, "id">;
// Immutable API response
type UserResponse = Readonly<PublicUser>;
// Lookup table keyed by id
type UserMap = Record<number, PublicUser>;12. Useful Custom Utilities
These are not built in but appear in most real codebases.
// Make specific keys optional
type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
type NewUser = PartialBy<User, "id">;
// Make specific keys required
type RequiredBy<T, K extends keyof T> = T & Required<Pick<T, K>>;
// Deep readonly
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};
// Deep partial
type DeepPartial<T> = {
[K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};
// At least one property required
type AtLeastOne<T, K extends keyof T = keyof T> =
K extends keyof T ? Required<Pick<T, K>> & Partial<Omit<T, K>> : never;
// Prettify — flattens intersections so hover tooltips are readable
type Prettify<T> = { [K in keyof T]: T[K] } & {};Prettify is a small trick worth knowing: it turns A & B & C into a single flat object in editor tooltips, which makes debugging complex types far easier.
13. satisfies With Utilities
type Theme = Record<"primary" | "secondary", string>;
// With a type annotation, literal values widen
const themeA: Theme = { primary: "#6366f1", secondary: "#8b5cf6" };
themeA.primary; // string
// With satisfies, they stay literal AND get checked
const themeB = {
primary: "#6366f1",
secondary: "#8b5cf6",
} satisfies Theme;
themeB.primary; // "#6366f1"You get the validation of an annotation without losing the narrow inferred type. Introduced in TypeScript 4.9 and worth mentioning.
14. Interview Summary
If asked "which utility types do you use most?" — the honest working answer:
Partial— update DTOs and optional configOmit/Pick— API request and response shapes derived from one entity typeRecord— exhaustive lookup tables keyed by a unionReturnType+Awaited— deriving types from functions so they never drift
Then add the reasoning: "The point is a single source of truth. One User type, and every DTO derived from it — so adding a field updates every related type automatically."