TypeScript · cheat sheet

TypeScript Utility Types

All 22 built-in TypeScript utility types with the exact type each one produces, how they are implemented, and the gotchas that trip people up in interviews.

Quiz me on TypeScript

All 22 built-in utility types, each with the type it produces; every example compiles under --strict.

Object modifiers

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

type Draft = Partial<User>;   // { id?: number; name?: string; email?: string }
type Full = Required<User>;   // { id: number; name: string; email: string }
type Frozen = Readonly<User>; // { readonly id: number; readonly name: string; ... }
TypeScript
  • Partial<T> makes every property optional: ideal for patches, update(id: number, patch: Partial<User>).
  • Required<T> removes ?, and the undefined that came with it.
  • Readonly<T> blocks reassignment at compile time only. Object.freeze(obj) freezes at runtime and returns Readonly<T>.
  • All three are shallow: nested objects keep their own modifiers.

Pick, Omit & Record

type Preview = Pick<User, 'id' | 'name'>; // { id: number; name: string }
type NewUser = Omit<User, 'id'>;          // { name: string; email?: string }

type Role = 'admin' | 'editor';
const permissions: Record<Role, string[]> = { admin: ['*'], editor: ['posts'] };
TypeScript
  • Pick<T, K> keeps the listed keys, and K must be keys of T.
  • Omit<T, K> removes keys, but K is not checked: Omit<User, 'typo'> compiles silently.
  • Record<K, V> builds an object type. With literal keys, every key is required (leaving out editor is an error).
  • Record<string, V> is the same type as { [key: string]: V }.
  • satisfies Record<string, string> checks the values while keeping the object’s exact keys.

Filtering unions

type Status = 'idle' | 'loading' | 'success' | 'error';
type Busy = Exclude<Status, 'idle'>;                // 'loading' | 'success' | 'error'
type Done = Extract<Status, 'success' | 'error'>;   // 'success' | 'error'
type Name = NonNullable<string | null | undefined>; // string

type Shape = { kind: 'circle'; r: number } | { kind: 'square'; side: number };
type Circle = Extract<Shape, { kind: 'circle' }>;   // { kind: 'circle'; r: number }
TypeScript
  • Exclude, Extract and NonNullable filter union members; to drop object keys, use Omit.
  • Extract with an object pattern pulls one variant out of a discriminated union.
  • NonNullable<T> is defined as T & {}.

Functions & classes

function createUser(name: string, age?: number) {
  return { id: 1, name, age };
}
type Args = Parameters<typeof createUser>;     // [name: string, age?: number | undefined]
type Made = ReturnType<typeof createUser>;     // { id: number; name: string; age: number | undefined }
type First = Parameters<typeof createUser>[0]; // string

class ApiClient { constructor(public baseUrl: string, public timeout = 5000) {} }
type CtorArgs = ConstructorParameters<typeof ApiClient>; // [baseUrl: string, timeout?: number]
type Client = InstanceType<typeof ApiClient>;            // ApiClient
TypeScript
  • Parameters and ReturnType take a function type: write typeof fn, not fn.
  • On an overloaded function, ReturnType uses the last overload.
  • ConstructorParameters and InstanceType take typeof Class, because ApiClient used as a type already means the instance.

this helpers

function greet(this: { name: string }, greeting: string) {
  return `${greeting}, ${this.name}`;
}
type Ctx = ThisParameterType<typeof greet>;   // { name: string }
type Plain = OmitThisParameter<typeof greet>; // (greeting: string) => string
const hi: Plain = greet.bind({ name: 'Ada' });
TypeScript
  • ThisParameterType<F> reads the this parameter (unknown if there is none).
  • OmitThisParameter<F> removes it, which is what bind gives you.
  • ThisType<T> is a marker: in an object literal typed as M & ThisType<X>, this is X. It needs noImplicitThis, which strict enables.
type Options<D, M> = { data: D; methods: M & ThisType<D & M> };
declare function define<D, M>(options: Options<D, M>): D & M;

const counter = define({
  data: { count: 0 },
  methods: { inc() { this.count++; } }, // this: { count: number } & { inc(): void }
});
TypeScript

Awaited & NoInfer

type N = Awaited<Promise<Promise<number>>>;         // number
async function loadUser() { return { id: 1 }; }
type Loaded = Awaited<ReturnType<typeof loadUser>>; // { id: number }

function pick<T extends string>(options: T[], fallback: NoInfer<T>): T {
  return options[0] ?? fallback;
}
pick(['sm', 'md', 'lg'], 'md'); // ok
pick(['sm', 'md', 'lg'], 'xl'); // error: '"xl"' is not assignable to '"lg" | "md" | "sm"'
TypeScript
  • Awaited<T> unwraps like await: recursively, including thenables. It is how Promise.all types its result.
  • NoInfer<T> (TypeScript 5.4+) stops an argument from being used to infer T. Without it, T widens to include 'xl' and the typo compiles.

String manipulation

type Method = Uppercase<'get'>;   // 'GET'
type Lower = Lowercase<'POST'>;   // 'post'
type Key = Uncapitalize<'UserId'>; // 'userId'
type Handler = `on${Capitalize<'click' | 'focus'>}`; // 'onClick' | 'onFocus'

type Getters<T> = { [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K] };
type UserGetters = Getters<{ id: number; name: string }>; // { getId: () => number; getName: () => string }
TypeScript
  • Uppercase, Lowercase, Capitalize and Uncapitalize are compiler intrinsics for string literal types, applied to each member of a union.
  • Combine them with template literal types and key remapping (as) to derive whole APIs.

How they’re built

type MyPartial<T> = { [K in keyof T]?: T[K] };
type MyPick<T, K extends keyof T> = { [P in K]: T[P] };
type MyOmit<T, K extends PropertyKey> = MyPick<T, Exclude<keyof T, K>>;
type MyExclude<T, U> = T extends U ? never : T;
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
TypeScript
  • Mapped types ([K in keyof T]) power Partial, Required, Readonly, Pick and Record. The -? and -readonly modifiers remove flags.
  • Conditional types power Exclude, Extract and the infer-based helpers. A bare type parameter like T in T extends U distributes over unions.

Gotchas & DIY helpers

type DeepPartial<T> = { [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K] };
type Mutable<T> = { -readonly [K in keyof T]: T[K] };
type DistributiveOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never;
TypeScript
  • Omit is not distributive. Omit<Shape, 'kind'> keeps only the keys shared by every member, then drops kind, leaving {}. DistributiveOmit<Shape, 'kind'> gives { r: number } | { side: number }.
  • Shallow by design: use DeepPartial for nested config objects.
  • Compile-time only: Readonly and Mutable never change runtime behavior.
  • Interview favorites: implement Pick, Omit, Exclude and ReturnType from memory, then explain why Omit accepts any key.
esc