Back to journal
·2 min readProgrammingTypescriptWeb DevelopmentBest Practices

Understanding TypeScript Utility Types

Partial, Required, Readonly, Pick, Omit, Record, ReturnType with real examples and the footguns I've hit.

ShareCopy failed

Your User, UpdateUser, and PublicUser interfaces start copying each other. Utility types stop that drift.

They're built-in transforms on existing types. Small syntax, big payoff if you hate maintaining five nearly identical interfaces.

Partial

Makes every property optional. Perfect for PATCH bodies.

interface User {
  id: number;
  name: string;
  email: string;
}

function updateUser(id: number, data: Partial<User>) {
  // data can include any subset of User props
}

updateUser(1, { name: "Pantelis" });

Required

Flips optional fields to required. Handy after you've filled defaults.

interface Config {
  cache?: boolean;
  retries?: number;
}

const fullConfig: Required<Config> = {
  cache: true,
  retries: 3,
};

Readonly

Compile-time lock on reassignment. Runtime objects can still mutate unless you freeze them.

interface Settings {
  theme: string;
  language: string;
}

const appSettings: Readonly<Settings> = {
  theme: "dark",
  language: "en",
};

appSettings.theme = "light"; // error

Pick

Subset of keys.

type PublicUser = Pick<User, "id" | "name" | "email">;

Omit

Drop keys you must not expose.

type SafeUser = Omit<User, "password">;

Record

Typed map from keys to values.

type Role = "admin" | "editor" | "viewer";

const permissions: Record<Role, string[]> = {
  admin: ["read", "write", "delete"],
  editor: ["read", "write"],
  viewer: ["read"],
};

ReturnType

Grab what a function returns without duplicating the shape.

function createUser() {
  return { id: 1, name: "Pantelis", email: "dev@ptheodosiou.com" };
}

type UserReturn = ReturnType<typeof createUser>;

Gotchas

Partial is shallow. Nested objects stay strict unless you write a DeepPartial.

Readonly won't stop someone mutating at runtime.

Stacking ten Pick/Omit chains gets unreadable. Sometimes a new base interface is clearer.

Record<string, any> is just an escape hatch. Be specific when you can.

ReturnType on overloaded functions can infer a union you didn't expect.

Quick reference

Type Does Typical use
Partial<T> optional fields PATCH
Required<T> all required after defaults
Readonly<T> no reassignment config
Pick<T, K> keep keys public DTO
Omit<T, K> drop keys hide secrets
Record<K, T> key map permissions
ReturnType<T> fn return type stay in sync

Utility types won't fix bad modeling, but they cut duplicate interfaces and keep types aligned with real data. Less typing, fewer lies in your types.

ShareCopy failed