Skip to content

4. Utility Types ​

Built-in generic types that transform other types. Know these ten cold — they come up constantly.


Quick Reference ​

UtilityDoes
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> ​

ts
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 changing

The canonical PATCH endpoint type.


2. Required<T> ​

ts
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> ​

ts
const config: Readonly<Config> = { host: "x", port: 80 };
// config.port = 90;   // Error

Shallow only — nested objects stay mutable. as const is usually better for literals.


4. Pick<T, K> and Omit<T, K> ​

ts
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 ​

ts
type T = Omit<User, "nonExistentKey">;   // no error — just returns User

A known wart. Pick does error on a bad key.


5. Record<K, V> ​

ts
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.

ts
// 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.

ts
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 | number

7. NonNullable<T> ​

ts
type MaybeUser = User | null | undefined;
type DefiniteUser = NonNullable<MaybeUser>;   // User

8. ReturnType<F>, Parameters<F>, Awaited<T> ​

ts
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 unwrapped

Why This Matters ​

Deriving a type from a function means it can never drift from the implementation. Very common with API clients and Zod schemas.

ts
// 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 ​

ts
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 typed

A typed wrapper/decorator with no loss of signature.


10. String Manipulation Types ​

ts
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:

ts
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.

ts
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.

ts
// 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 ​

ts
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 config
  • Omit / Pick — API request and response shapes derived from one entity type
  • Record — exhaustive lookup tables keyed by a union
  • ReturnType + 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."

© 2025 DDocs · Dipak's Documentation Guide