Design.dev design.dev

TypeScript utility types

TypeScript ships helpers that transform existing types. They live in the standard library and compose with keyof, indexed access, and generics.

Partial, Required, Readonly

type User = { id: string; name: string; email: string };

// All keys optional — useful for PATCH payloads
type UserPatch = Partial<User>;

// Every key required (strip ?)
type Strict = Required<Partial<User>>;

// Shallow readonly — nested objects stay mutable unless you map them
type FrozenUser = Readonly<User>;

Partial and Readonly are shallow; nested objects keep their original mutability unless you map them yourself.

Pick and Omit

type User = { id: string; name: string; email: string; role: 'admin' | 'member' };

type PublicUser = Pick<User, 'id' | 'name'>;
type UserWithoutEmail = Omit<User, 'email'>;

// Common: props for a presentational component
type UserCardProps = Pick<User, 'name' | 'role'>;

Pick<T, K> keeps only keys K. Omit<T, K> removes keys K. With unions, Omit distributes over members.

Record<Keys, Value>

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

const defaultCaps: Record<Role, boolean> = {
  admin: true,
  editor: true,
  viewer: false,
};

// Same as { [K in Role]: boolean }

Use Record when every key in a union should map to the same value type.

ReturnType and Parameters

function createUser(name: string, role: 'admin' | 'member') {
  return { id: crypto.randomUUID(), name, role };
}

type NewUser = ReturnType<typeof createUser>;
type CreateUserArgs = Parameters<typeof createUser>;
// [name: string, role: 'admin' | 'member']

Works with overloaded functions by using the last overload’s signature. For class instances, use InstanceType<typeof MyClass>.

Exclude, Extract, NonNullable

type T = 'a' | 'b' | 'c';
type NoA = Exclude<T, 'a'>;     // 'b' | 'c'
type JustA = Extract<T, 'a' | 'z'>; // 'a'

type MaybeId = string | null | undefined;
type Id = NonNullable<MaybeId>;   // string

Recipes

Update handler for a subset of keys

type User = { name: string; email: string; age: number };

function patchUser<K extends keyof User>(key: K, value: User[K]): Pick<User, K> {
  return { [key]: value } as Pick<User, K>;
}

The as Pick<User, K> assertion satisfies the compiler for a single known key; it does not validate value at runtime.

Props except one key

type Props = { title: string; body: string; footnote?: string };
type HeaderOnly = Omit<Props, 'body' | 'footnote'>;

Nullable-to-optional for forms

type ApiUser = { id: string; nickname: string | null };
type FormUser = Omit<ApiUser, 'nickname'> & { nickname?: string };