Skip to content
C Codeloom
TypeScript

TypeScript Utility Types Cheatsheet

A practical reference for TypeScript utility types: Partial, Pick, Omit, Record, Readonly, ReturnType, and a dozen more with real examples.

·5 min read · By Codeloom
Intermediate 10 min read

What you'll learn

  • The most-used utility types with examples
  • When to reach for each one
  • How they compose into custom helpers
  • Patterns for transforming object types
  • Pitfalls when combining them

Prerequisites

  • Comfortable with basic TypeScript

TypeScript ships with a small but powerful set of generic utility types that transform other types. Once you know them, you stop writing repetitive interfaces and start expressing intent: “the same as User but everything is optional,” “a map from action names to handlers,” “the return type of this function.” This cheatsheet walks through the ones you will use weekly.

What and why

A utility type is a generic that takes one type and returns a derived type. They live in lib.es5.d.ts and friends, always in PascalCase. They are not runtime constructs; they exist only in the type system. The benefit is keeping a single source of truth: change User and every type derived from it updates automatically.

Mental model

Think of each utility as a pure function in the type universe. It takes types in, returns a new type out. They compose like any other function.

User -> Partial<User>          all optional
User -> Required<User>         all required
User -> Readonly<User>         no mutation
User -> Pick<User, 'id'>       subset of keys
User -> Omit<User, 'id'>       complement of keys
'a'|'b' -> Record<K, V>        object map
fn   -> ReturnType<fn>         result of a function
fn   -> Parameters<fn>         args as tuple
Common transformations

Hands-on examples

Given:

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

Partial<T> makes every property optional. Useful for patch payloads.

function updateUser(id: string, patch: Partial<User>) { /* ... */ }
updateUser('1', { name: 'Yash' }); // ok

Required<T> is the inverse; every property becomes required.

type FullUser = Required<User>; // age is now required

Readonly<T> freezes the shape.

const u: Readonly<User> = { id: '1', name: 'a', email: 'b' };
// u.name = 'c'; // error

Pick<T, K> selects a subset of keys.

type UserSummary = Pick<User, 'id' | 'name'>;

Omit<T, K> removes keys.

type UserPublic = Omit<User, 'email'>;

Record<K, V> builds an object type with the given keys and value type.

type Roles = Record<'admin' | 'editor' | 'viewer', string[]>;

ReturnType<typeof fn> and Parameters<typeof fn> extract the shape of a function.

function createUser(name: string, age: number) {
  return { id: crypto.randomUUID(), name, age };
}
type CreatedUser = ReturnType<typeof createUser>;
type CreateArgs = Parameters<typeof createUser>; // [string, number]

Awaited<T> unwraps a promise (recursively).

type R = Awaited<Promise<Promise<number>>>; // number

NonNullable<T> strips null and undefined.

type S = NonNullable<string | null | undefined>; // string

Extract<T, U> and Exclude<T, U> filter unions.

type Color = 'red' | 'green' | 'blue' | 'transparent';
type Solid = Exclude<Color, 'transparent'>; // 'red' | 'green' | 'blue'
type Primary = Extract<Color, 'red' | 'blue' | 'yellow'>; // 'red' | 'blue'

InstanceType<typeof Ctor> and ConstructorParameters<typeof Ctor> work on classes.

class Logger { constructor(public name: string) {} }
type L = InstanceType<typeof Logger>;
type LArgs = ConstructorParameters<typeof Logger>; // [string]

Composing utilities

Real codebases combine them. Suppose you want a “create” type that strips server-generated fields and makes some optional:

type CreateUserInput = Partial<Pick<User, 'age'>> & Omit<User, 'id' | 'age'>;
// { name: string; email: string; age?: number; }

Or a deeply readonly type:

type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};

That last one is not built in; the standard Readonly is shallow. Writing your own helpers on top of the built-ins is how mature codebases evolve.

Common pitfalls

  • Omit does not enforce that the keys exist on the source. Misspelled keys silently produce the same type. Use a constrained version if you need stricter behavior.
  • Partial is shallow. Nested objects stay required. Combine with a DeepPartial helper if needed.
  • Record defaults to including every key, even ones you did not assign at runtime. Combine with Partial<Record<K, V>> if assignments are optional.
  • ReturnType of an overloaded function picks the last overload. Surprising if you rely on the first signature.

Best practices

  • Derive types from a single source. Define User once and let Partial, Pick, and Omit produce variants.
  • Name derived types meaningfully. UpdateUserPatch reads better than Partial<User> everywhere.
  • Use satisfies (TypeScript 4.9+) when you want to check shape conformance without losing literal types:
const palette = {
  red: '#f00',
  blue: '#00f',
} satisfies Record<string, `#${string}`>;
  • Prefer composition over deeply nested conditional types unless you really need them. Readability matters more than cleverness.

FAQ

Where are utility types defined? In TypeScript’s lib files (lib.es5.d.ts, lib.esnext.d.ts). You can Cmd-click them in your editor.

Do they have runtime cost? Zero. They are erased at compile time.

Can I make my own? Yes, with generics, conditional types, and mapped types. Look at how the built-ins are written; they are short and instructive.

Is Pick the same as Omit with the complement? Logically yes, but Pick is positive (list what to include) and Omit is negative (list what to exclude). Pick whichever reads better.