TypeScriptCheatsheet

TypeScript Types Cheatsheet📋

Every TypeScript type construct you actually reach for in production, in one place. Utility types, conditional types, mapped types, generics, type guards, and the patterns that tie them together. No fluff, no toy examples. Bookmark it.

Quick Reference

NameSyntaxDescription
Partial<T>Partial<User>Makes all properties optional. Use for PATCH/update payloads.
Required<T>Required<Config>Makes all properties required. Strips every ? modifier.
Pick<T, K>Pick<User, "id" | "name">Keeps only the listed properties. Good for API response shaping.
Omit<T, K>Omit<User, "password">Drops the listed properties. Inverse of Pick.
Record<K, V>Record<string, unknown>Object type with keys K and values V. Great for lookup tables.
Readonly<T>Readonly<State>Makes all properties readonly. Shallow only, does not freeze nested objects.
ReturnType<T>ReturnType<typeof fn>Extracts the return type of a function. Pair with typeof for values.
Parameters<T>Parameters<typeof fn>Extracts parameter types as a tuple. Index with [0], [1] for individual params.
Awaited<T>Awaited<Promise<Response>>Recursively unwraps Promise types. Handles nested Promise<Promise<T>>.
Extract<T, U>Extract<Event, { type: "click" }>Keeps union members assignable to U. Filters discriminated unions.
Exclude<T, U>Exclude<Status, "error">Removes union members assignable to U. Returns the rest.
NonNullable<T>NonNullable<string | null>Strips null and undefined from a union. Shorthand for Exclude<T, null | undefined>.
keyof Tkeyof UserUnion of all property keys: "id" | "name" | "email". Foundation of type-safe access.
typeof valuetypeof configCaptures the type of a runtime value. Combine with as const for literal types.
inferT extends Promise<infer R> ? R : TDeclares a type variable inside an extends clause. The compiler fills it in.

Utility Types

Partial, Required, Readonly

Partial<T> | Required<T> | Readonly<T>

Partial makes every property optional (for updates), Required strips all ? modifiers (for resolved configs), and Readonly prevents property reassignment. All three are mapped types under the hood: { [P in keyof T]?: T[P] }.

typescript

Tips

  • Readonly is shallow. state.items.push('x') still works because the array reference is frozen, not the array itself. Use a custom DeepReadonly for nested immutability.
  • Partial<T> is the standard type for PATCH-style update functions. Combine with Omit to exclude fields that should never be updated: Partial<Omit<User, 'id'>>.
  • Required uses -? to strip optionality. You can use this modifier in your own mapped types too.

Pick, Omit, Record

Pick<T, K> | Omit<T, K> | Record<K, V>

Pick selects properties by name, Omit removes them, and Record builds a dictionary from a key union and value type. Use Pick when listing inclusions is clearer; use Omit when exclusions are fewer.

typescript

Tips

  • Omit does not warn if you omit a key that does not exist. Omit<User, "typo"> compiles silently. Consider a strict Omit using Extract<K, keyof T> if this matters.
  • Record<string, unknown> is the type-safe replacement for the object type or {} when you need an arbitrary key-value map.
  • Chain utility types: Partial<Omit<User, 'id' | 'createdAt'>> gives you an update payload with no id and no createdAt, all fields optional.

Extract, Exclude, NonNullable

Extract<T, U> | Exclude<T, U> | NonNullable<T>

These operate on union members, not object properties. Exclude removes members assignable to U, Extract keeps them, and NonNullable is just Exclude<T, null | undefined>. They rely on distributive conditional types.

typescript

Tips

  • Do not confuse Exclude/Extract with Omit/Pick. Exclude filters union members; Omit removes object properties. Completely different operations.
  • Extract with discriminated unions is the cleanest way to narrow to a specific variant: Extract<Event, { kind: 'click' }>.
  • NonNullable is essential after optional chaining. If you have user?.name, wrap the type with NonNullable<typeof user.name> when you have already checked for null.

ReturnType, Parameters, Awaited

ReturnType<T> | Parameters<T> | Awaited<T>

ReturnType extracts what a function returns, Parameters extracts its argument tuple, and Awaited recursively unwraps Promise types. Always pair these with typeof when working with function values (not types).

typescript

Tips

  • ReturnType<typeof fn> is one of the most-used patterns in TypeScript. It keeps derived types in sync with the source function automatically.
  • Awaited handles nested promises: Awaited<Promise<Promise<string>>> is just string. No need for custom recursive unwrapping.
  • Index into Parameters for individual param types: Parameters<typeof fn>[0] gives the first parameter's type.

Conditional Types

Basic conditional types

T extends U ? X : Y

Conditional types bring if/else logic to the type system. The extends keyword checks assignability: if T is assignable to U, resolve to X, otherwise Y. This is how Exclude, Extract, ReturnType, and most utility types are built.

typescript

Tips

  • extends in a conditional type means 'is assignable to', not 'inherits from'. string extends string | number is true because string is assignable to the union.
  • Conditional types distribute over unions by default. IsString<string | number> evaluates to true | false (each member checked separately). Wrap in [T] extends [U] to check the union as a whole.
  • Chain conditionals for multi-branch logic, but extract intermediate types with names if it gets deeper than 3 levels.

The infer keyword

T extends (...args: infer P) => infer R ? [P, R] : never

infer declares a type variable inside an extends clause that TypeScript fills in from context. It can capture return types, parameter types, promise contents, tuple elements, and template literal parts. Only valid inside the condition of a conditional type.

typescript

Tips

  • Constrain the generic before inferring. Use T extends (...args: any) => any before trying to infer R from the return position. This gives better error messages.
  • For deep promise unwrapping, use recursion: type Deep<T> = T extends Promise<infer U> ? Deep<U> : T. Or just use the built-in Awaited.
  • You can infer from template literal positions too: T extends `on${infer Event}` captures the event name from handler strings.

Mapped Types

Basic mapped types and modifiers

{ [K in keyof T]: NewType }

Mapped types iterate over keys with [K in keyof T] and produce a new type for each property. Modifiers +/- control readonly and ?. Key remapping with as lets you rename or filter keys during the mapping.

typescript

Tips

  • Use -readonly to strip readonly, -? to strip optionality. The + prefix is implicit and rarely written.
  • When using Capitalize in the as clause, always write Capitalize<string & K> to exclude symbol keys that cannot be capitalized.
  • Return never from the as clause to filter out properties: [K in keyof T as T[K] extends Function ? never : K] keeps only data properties.

Key remapping and filtering

{ [K in keyof T as NewKey]: T[K] }

The as clause in mapped types transforms or filters keys. Return never to drop a key. Use conditional types in the value position to selectively transform property types (like converting Date to string for JSON).

typescript

Tips

  • Filtering with as + never is cleaner than chaining Pick with a custom key-extraction type. Prefer the single mapped type.
  • You can combine filtering and renaming in one pass: filter to function properties AND prefix with 'on' in the same as clause.
  • For deep transformations (nested objects), make the mapped type recursive: T[K] extends object ? DeepSerialize<T[K]> : T[K].

Template Literal Types

String type construction

`prefix-${Union}`

Template literal types use backtick syntax to construct string types at compile time. When a union is interpolated, TypeScript generates every possible combination (cross product). Use Capitalize, Uppercase, Lowercase, and Uncapitalize for casing transforms.

typescript

Tips

  • Union distribution is multiplicative. Three unions of 10 members each produce 1,000 types. Keep interpolated unions small to avoid slowing down the compiler.
  • Combine template literals with mapped types: { [E in Events as `on${Capitalize<E>}`]: (e: E) => void } generates a typed event handler map.
  • Pattern-match with infer: T extends `on${infer Event}` ? Uncapitalize<Event> : never extracts the event name from a handler string.

Intrinsic string types

Uppercase<S> | Lowercase<S> | Capitalize<S> | Uncapitalize<S>

Four compiler-intrinsic types for string case manipulation. They distribute over unions and work inside template literal positions. Capitalize and Uncapitalize touch only the first character; Uppercase and Lowercase transform all characters.

typescript

Tips

  • These types only work on string literal types. Uppercase<string> just returns string. Use them with literal unions or as const values.
  • The on + Capitalize pattern is the de facto standard for generating handler names in the TypeScript ecosystem.
  • Combine pattern matching with intrinsics to parse and transform strings at the type level: T extends `${infer A}_${infer B}` ? `${A}${Capitalize<B>}` : T converts snake_case to camelCase.

Type Guards

typeof and instanceof guards

typeof x === "string" | x instanceof Error

typeof checks narrow to primitive types (string, number, boolean, symbol, bigint, undefined, object, function). instanceof checks narrow to class instances. TypeScript narrows the type in the truthy branch automatically.

typescript

Tips

  • typeof null === 'object' is a JavaScript quirk that still applies. Use value === null for null checks.
  • instanceof does not work across iframes or different realms because each has its own constructor references.
  • Check more specific classes first. if (err instanceof TypeError) before if (err instanceof Error), because TypeError extends Error.

User-defined type guards

function isX(value: unknown): value is X

A type predicate (value is Type) tells TypeScript to narrow the parameter's type in the calling scope when the function returns true. Use for runtime validation of unknown data (API responses, parsed JSON, external inputs).

typescript

Tips

  • The function must actually validate at runtime what the return type claims. A bad predicate that always returns true is worse than no predicate at all.
  • For discriminated unions, you usually do not need a custom guard. A simple if (shape.kind === 'circle') narrows automatically.
  • Use assertion functions (asserts value is Type) when you want to throw instead of returning false. They narrow in the code that follows the call.

Generic Patterns

Constrained generics

<T extends Constraint>

Generics with constraints (extends) restrict what types can be passed. The classic pattern is <T, K extends keyof T> for type-safe property access. Generic defaults (= unknown) provide a fallback when the type is not specified.

typescript

Tips

  • Always constrain generics to the narrowest type that makes sense. <T extends object> is better than bare <T> when you need an object.
  • Use generic defaults for optional type parameters: ApiResponse<T = unknown> lets callers skip the type argument.
  • The getProperty pattern (<T, K extends keyof T>(obj: T, key: K): T[K]) is the foundation of type-safe libraries. Study it until it is second nature.

Generic inference and typeof

typeof + as const + keyof

Define constants once with as const, then derive all types with typeof, keyof, and indexed access. This eliminates type/value duplication. Changes to the runtime object automatically update all derived types.

typescript

Tips

  • as const is the key. Without it, typeof ROUTES gives { home: string; ... } instead of literal types. Always use as const for config objects you want to derive types from.
  • (typeof ARR)[number] extracts the element type from a const array. This is the standard way to turn a runtime array into a union type.
  • Use satisfies with as const when you need both literal types and validation: const x = {...} as const satisfies Record<string, string>.

Common Patterns

API response wrapper type

typescript

A discriminated union with success as the discriminant. TypeScript narrows the type in each branch, giving you data in the success branch and error in the failure branch. This pattern eliminates entire classes of null-check bugs in API code.

Form state types from a schema

typescript

Derive all form-related types from a single schema interface. FormErrors and FormTouched use Record<keyof T, ...> to mirror the field names. This guarantees that adding a field to the schema updates validation, touched tracking, and form state automatically.

Typed event emitter

typescript

A generic event emitter where the event map type constrains both event names and payload shapes. Each handler receives the exact payload type for its event. Adding a new event to EventMap automatically makes it available with full type checking.

Watch Out For

Readonly<T> is shallow. You freeze the top-level properties but nested objects and arrays are still mutable.

Build a DeepReadonly type: type DeepReadonly<T> = { readonly [K in keyof T]: T[K] extends object ? T[K] extends Function ? T[K] : DeepReadonly<T[K]> : T[K] }. Or use Object.freeze at runtime, which has the same shallow limitation in JS. For production immutability, consider Immer.

Distributive conditional types catch people off guard. Exclude<string | number, string> returns number, but Exclude<never, string> returns never (not the identity). never distributes to zero iterations.

Wrap in a tuple to prevent distribution when you need to check the union as a whole: [T] extends [U] ? X : Y. Use the wrapped form for IsNever<T>: [T] extends [never] ? true : false. Without the wrapper, the condition is never entered because never has zero members.

Omit<T, K> does not error when K includes keys that do not exist on T. Omit<User, 'nonexistent'> compiles silently and does nothing.

If you want strict Omit that catches typos, define your own: type StrictOmit<T, K extends keyof T> = Omit<T, K>. The extends keyof T constraint ensures K is a valid key. The built-in Omit uses K extends string | number | symbol, which is intentionally loose.

Using as const on an object literal makes every property readonly and literal-typed, but assigning it to a wider type first loses the literal types. const x: Record<string, string> = { a: 'b' } as const wipes out the literal.

Declare the variable without a type annotation and let inference do its job: const x = { a: 'b' } as const. If you need validation against a shape, use satisfies instead of a type annotation: const x = { a: 'b' } as const satisfies Record<string, string>. This preserves literal types while checking the structure.

Type predicates (is) lie to the compiler if the runtime check is wrong. function isString(x: unknown): x is string { return true } narrows to string even when x is a number.

Treat type predicates as a contract. The runtime check must exactly match what the return type claims. Test your type guards with edge cases. Consider using Zod's .safeParse() or io-ts for schema validation instead of hand-written predicates for complex shapes.

Master TypeScript with Stanza

Go beyond the cheatsheet with hands-on lessons and challenges.

Dive Deeper