Skip to content

TypeScript Cheat Sheet

TypeScript adds static types to JavaScript: primitives, structured types, generics, and compile-time checks that erase at runtime when transpiled to JS.

How to use this TypeScript cheat sheet

Core syntax covers primitives, arrays, tuples, interfaces, type aliases, unions, and narrowing with typeof, in, and discriminated unions. Utility types like Partial, Pick, and Record reshape existing types without duplication.

Generics parameterize types and functions; satisfies validates shape without widening; as const freezes literals. Enable strict in tsconfig for null safety, implicit any errors, and reliable inference across your codebase.

Quick TypeScript example

interface User {
  id: string;
  name: string;
  role: 'admin' | 'member';
}

type UserId = User['id'];

function getUser(id: UserId): Promise<User | undefined> {
  return fetch('/api/users/' + id).then((r) => r.json());
}

const config = {
  theme: 'dark',
  retries: 3,
} as const satisfies { theme: 'dark' | 'light'; retries: number };

Primitives & Collections

Syntax Example Notes
Primitives let n: number; let s: string; let ok: boolean; number includes all JS numbers (no int/float split)
Arrays string[] or Array&lt;string&gt; Homogeneous arrays; readonly T[] for immutability
Tuples [string, number] Fixed-length arrays with typed positions
Literal types type Dir = "up" | "down" Restrict to specific string/number values
any vs unknown unknown requires narrowing before use Prefer unknown over any for external data
void & never (): void vs (): never void returns undefined; never means no return (throws)
Optional props email?: string Equivalent to email: string | undefined
Readonly readonly string[] Prevents mutating methods on arrays and tuples

Interfaces vs Type Aliases

Feature Example Notes
Interface interface Point { x: number; y: number; } Extendable; good for object shapes and classes
Type alias type Point = { x: number; y: number; } Can represent unions, tuples, mapped types
Extends interface A extends B, C {} Multiple interface inheritance
Intersection type AB = A & B Merge types; conflicts become never
Declaration merge interface Window { myApp: App; } Interfaces merge; type aliases do not
Implement class C implements I {} Classes implement interfaces, not type aliases with unions
Index signature [key: string]: number Dynamic keys on objects
Call signature type Fn = (x: number) => string Describe function types in type aliases

Narrowing & Generics

Pattern Example Notes
typeof if (typeof x === "string") Narrows primitives
in operator if ("swim" in pet) Narrows union by property presence
Discriminant if (action.type === "ADD") Tagged unions with shared literal field
Generic fn function id&lt;T&gt;(x: T): T Preserve input type through function
Constraint &lt;T extends { id: string }&gt; Limit generic to shapes with id
Generic interface interface Box&lt;T&gt; { value: T; } Reusable typed containers
Default type param &lt;T = string&gt; Fallback when type argument omitted
infer keyword type R = T extends (...args: infer A) =&gt; infer R ? R : never Extract types inside conditional types

Utility Types & Strict Config

Utility Example Purpose
Partial Partial&lt;User&gt; All properties optional
Required Required&lt;User&gt; All properties required
Pick Pick&lt;User, "id" | "name"&gt; Subset of properties
Omit Omit&lt;User, "password"&gt; Exclude properties
Record Record&lt;string, number&gt; Object with keyed index type
ReturnType ReturnType&lt;typeof fn&gt; Extract function return type
satisfies const c = { a: 1 } satisfies Config Validate without widening inferred literals
strict flags "strict": true in tsconfig Enables strictNullChecks, noImplicitAny, and related checks

Discriminated union narrowing

type Result =
  | { ok: true; data: string }
  | { ok: false; error: string };

function handle(r: Result) {
  if (r.ok) return r.data.toUpperCase();
  throw new Error(r.error);
}

Shared literal field (ok) enables exhaustive narrowing in if/switch.

Mapped type for form state

type FormErrors<T> = Partial<Record<keyof T, string>>;

interface LoginForm { email: string; password: string; }
const errors: FormErrors<LoginForm> = { email: 'Required' };

Mapped types derive new shapes from existing interfaces.

Strict tsconfig essentials

{
  "compilerOptions": {
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,
    "moduleResolution": "bundler",
    "skipLibCheck": true
  }
}

strict plus noUncheckedIndexedAccess catches most real-world bugs early.

Common mistakes

  • Using any to silence errors instead of unknown plus narrowing.
  • Overusing type assertions (as) when satisfies or generics would be safer.
  • Defining duplicate interfaces and types for the same shape without a single source of truth.
  • Leaving strict: false and missing null/undefined bugs until runtime.

Key takeaways

  • Prefer interfaces for object contracts; type aliases for unions and advanced types.
  • Narrow unions with typeof, in, and discriminant fields before accessing properties.
  • Generics preserve relationships between inputs and outputs.
  • Enable strict and use satisfies to keep literal types while validating shape.

Pro Tip

When migrating JS, enable allowJs and checkJs first, then flip strict on file-by-file using // @ts-strict or project references.