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