TypeScript cheatsheetv6.0.3

Comprehensive reference for modern TypeScript — types, generics, narrowing, utility types, and 5.x → 6.0 features.
Each section links to the official handbook.

Primitive types docs ↗

let name: string = "alice"
let age: number = 30
let ok: boolean = true
let big: bigint = 9007199254740993n
let sym: symbol = Symbol("id")
let nothing: null = null
let notSet: undefined = undefined

Special

any        // turns off checking — avoid
unknown    // safe any — must narrow before use
never      // no value can exist (exhaustiveness)
void       // fn returns nothing usable
object     // any non-primitive
Avoid: the boxed types String, Number, Boolean (capitalised). Always use the lowercase primitives.

Variables & const docs ↗

// Inferred — preferred
const count = 42            // const → literal type 42
let total = 0              // let   → widened to number

// Explicit annotation
const users: string[] = []

as const

const role = "admin" as const     // type: "admin"
const point = { x: 1, y: 2 } as const
// { readonly x: 1; readonly y: 2 }

satisfies — type-check without widening

const palette = {
  red:  [255, 0, 0],
  blue: "#00f",
} satisfies Record<string, string | number[]>

palette.red[0].toFixed()   // ✓ still number[]
palette.blue.toUpperCase()  // ✓ still string — types preserved

Object types docs ↗

type User = {
  id: number
  name: string
  email?: string                  // optional
  readonly createdAt: Date       // readonly
  [key: string]: unknown          // index signature
}

Optional vs | undefined

// With exactOptionalPropertyTypes: true — these differ:
type A = { name?: string }             // key may be absent
type B = { name: string | undefined } // key required, value may be undefined

Index signatures

type Headers = { [name: string]: string }
type Matrix  = { [row: number]: { [col: number]: number } }

Arrays & tuples docs ↗

const a: number[]          = [1, 2, 3]
const b: Array<number>   = [1, 2]
const r: readonly number[] = [1, 2]

// Tuple
const pair: [string, number] = ["x", 1]

// Named tuple (labels improve hover info)
type Range = [start: number, end: number]

// Variadic tuple
type Args<T extends unknown[]> = [action: string, ...args: T]

Functions docs ↗

// Declaration
function add(a: number, b: number): number {
  return a + b
}

// Expression
const sub = (a: number, b: number): number => a - b

// Function type alias
type BinaryOp = (a: number, b: number) => number

// Optional / default / rest
function greet(name: string, tag?: string, punct: string = "!"): string { ... }
function sum(...xs: number[]): number { ... }

Overloads

function parse(s: string): number
function parse(s: string[]): number[]
function parse(s: string | string[]): number | number[] { ... }

Call signatures & constructors

type Logger = {
  (msg: string): void          // callable
  level: "info" | "warn"
}
type UserCtor = new (id: number) => User   // constructable

type vs interface docs ↗

typeinterface
Object shape
Union / primitive
Mapped / conditional
Declaration merging
extendsvia &native
Performance (large)fineslightly better
interface Animal { name: string }
interface Dog extends Animal { bark(): void }

type Animal2 = { name: string }
type Dog2    = Animal2 & { bark(): void }

// Declaration merging — interface only:
interface Window { myApi: Api }
Rule of thumb: use interface for public, extensible object shapes (especially library types); use type for unions, intersections, and computed types.

Unions & intersections docs ↗

type Id = string | number                     // union
type Named = { name: string }
type Aged  = { age: number }
type Person = Named & Aged                  // intersection

Distributive unions

type Boxed<T> = { value: T }
type BoxedSN  = Boxed<string | number>
// → { value: string } | { value: number }  ❌ NO — generic doesn't distribute
// Distribution only happens for naked type-params in conditional types:
type Box<T> = T extends unknown ? { value: T } : never
type Boxes = Box<string | number>
// → { value: string } | { value: number }

Literal types & narrowing docs ↗

type Direction = "north" | "south" | "east" | "west"
type Bit       = 0 | 1
type Status    = true | false

Narrowing flow

function area(s: string | number): number {
  if (typeof s === "string") return s.length   // s: string
  return s * s                                       // s: number
}

Narrowing operators

  • typeof — primitive checks
  • instanceof — class checks (respects Symbol.hasInstance)
  • in — property check
  • Array.isArray()
  • equality against literal: x === "a"
  • truthy/falsy: if (x) — narrows out null/undefined/0/""

Assertion functions

function assert(cond: unknown, msg?: string): asserts cond {
  if (!cond) throw new Error(msg)
}
assert(typeof input === "string")
input.toUpperCase()  // ✓ narrowed

Type guards & predicates docs ↗

User-defined predicate

function isString(x: unknown): x is string {
  return typeof x === "string"
}

Inferred predicates — 5.5+

const mixed = [1, "a", 2, "b"]
const nums = mixed.filter(x => typeof x === "number")
// nums: number[]  ✓  (was (string|number)[] before 5.5)

Property-based narrowing

function hasName(x: unknown): x is { name: string } {
  return typeof x === "object" && x !== null &&
         "name" in x && typeof (x as any).name === "string"
}

Discriminated unions docs ↗

type Shape =
  | { kind: "circle";  radius: number }
  | { kind: "square";  side: number }
  | { kind: "rect";    w: number; h: number }

function area(s: Shape): number {
  switch (s.kind) {
    case "circle": return Math.PI * s.radius ** 2
    case "square": return s.side * s.side
    case "rect":   return s.w * s.h
    default:        return assertNever(s)
  }
}
function assertNever(x: never): never {
  throw new Error(`unhandled: ${x}`)
}
Why this matters: if you add a new variant to Shape and forget a case, the assertNever call fails at compile time — the un-handled variant won't be never.

Generics docs ↗

function identity<T>(x: T): T { return x }

// Constraint
function pluck<T, K extends keyof T>(o: T, k: K): T[K] {
  return o[k]
}

// Default
type Result<T, E = Error> = { ok: true; value: T } | { ok: false; err: E }

const type parameters — 5.0+

function first<const T extends readonly unknown[]>(xs: T): T[0] {
  return xs[0]
}
const a = first(["a", "b", "c"])   // type: "a"  (not string)

NoInfer<T> — 5.4+

function createState<T>(initial: T, values: NoInfer<T>[]) { ... }
createState("red", ["red", "green", "blue"])
// T inferred from `initial`, not widened by `values`

Built-in utility types docs ↗

TypeEffect
Partial<T>all props optional
Required<T>all props required
Readonly<T>all props readonly
Pick<T, K>keep keys K
Omit<T, K>drop keys K
Record<K, V>object with key set K
Exclude<T, U>remove union members
Extract<T, U>keep matching members
NonNullable<T>strip null | undefined
ReturnType<F>fn return type
Parameters<F>fn args tuple
ConstructorParameters<C>ctor args
InstanceType<C>instance shape
Awaited<P>unwrap Promise (recursive)
NoInfer<T>block inference site (5.4+)
Uppercase<S>intrinsic string
Lowercase<S>"
Capitalize<S>"
Uncapitalize<S>"
type UserPatch   = Partial<User>
type UserPublic  = Omit<User, "password" | "token">
type ApiResult   = Awaited<ReturnType< typeof fetchUser >>

Conditional types & infer docs ↗

type IsString<T> = T extends string ? true : false

// infer — extract part of a type
type Return<F> = F extends (...a: any[]) => infer R ? R : never
type First<T>  = T extends [infer H, ...any[]] ? H : never
type UnwrapP<T> = T extends Promise<infer U> ? U : T

Distribution control

// Naked T → distributes over union
type ToArray<T> = T extends any ? T[] : never
type A = ToArray<string | number>  // string[] | number[]

// Wrap in tuple → block distribution
type ToArr2<T> = [T] extends [any] ? T[] : never
type B = ToArr2<string | number> // (string | number)[]

Mapped types docs ↗

// Basic
type Optional<T> = { [K in keyof T]?: T[K] }

// Modifiers: +readonly / -readonly / +? / -?
type Mutable<T>     = { -readonly [K in keyof T]: T[K] }
type RequiredKeys<T> = { [K in keyof T]-?: T[K] }

// Key remapping with `as`
type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
}
// Getters<{name: string; age: number}> →
// { getName(): string; getAge(): number }

// Filter keys (omit functions)
type DataOnly<T> = {
  [K in keyof T as T[K] extends Function ? never : K]: T[K]
}

Template literal types docs ↗

type Greeting = `hello, ${string}`
type Route    = `/${"users" | "posts"}/${string}`

// CSS-like enums
type Side   = "top" | "right" | "bottom" | "left"
type Margin = `margin-${Side}`
// "margin-top" | "margin-right" | "margin-bottom" | "margin-left"

// Parse with infer
type Split<S, D extends string> =
  S extends `${infer H}${D}${infer R}`
    ? [H, ...Split<R, D>]
    : [S]
type P = Split<"a.b.c", ".">  // ["a","b","c"]

Classes docs ↗

class Account {
  readonly id: string
  private  #balance: number     // JS private (truly hidden)
  protected log: string[] = []

  constructor(public name: string, id: string) {
    this.id = id
    this.#balance = 0
  }

  get balance(): number { return this.#balance }
  set balance(v: number) { this.#balance = v }

  static open(name: string): Account {
    return new Account(name, crypto.randomUUID())
  }
}

Abstract classes

abstract class Animal {
  abstract speak(): string
  describe(): string { return `I say ${this.speak()}` }
}

implements vs extends

interface Logger { log(msg: string): void }
class ConsoleLogger implements Logger {
  log(msg: string): void { console.log(msg) }
}
Tip: prefer #field (ECMAScript private) over private field — the former is enforced at runtime, the latter only at compile time.

Enums docs ↗

// Numeric enum
enum Direction { Up, Down, Left, Right }

// String enum (preferred — stable serialised values)
enum Role {
  Admin  = "admin",
  User   = "user",
  Guest  = "guest",
}

// const enum (inlined, no runtime object) — incompatible with isolatedModules
const enum Flag { A = 1, B = 2 }
Modern alternative: for new code, prefer a string-literal union + const object — works under erasableSyntaxOnly (5.8+) and avoids the runtime cost.
const Role = { Admin: "admin", User: "user" } as const
type Role = typeof Role[keyof typeof Role]

Modules docs ↗

// Named
export const add = (a, b) => a + b
import { add } from "./math.ts"

// Default
export default class User {}
import User from "./user.ts"

// Type-only — erased at runtime
import type { User } from "./user.ts"
export type { User }

// Re-export
export * from "./shapes.ts"
export { add as sum } from "./math.ts"

Import attributes — 5.3+

import data from "./data.json" with { type: "json" }
const mod = await import("./mod.ts", { with: { type: "json" } })

ESM relative imports under nodenext

// Always include the extension — Node strips types, doesn't resolve
import { add } from "./math.ts"   // or .js (transpiled)

Async & Promises docs ↗

async function fetchUser(id: string): Promise<User> {
  const res = await fetch(`/api/users/${id}`)
  if (!res.ok) throw new Error(res.statusText)
  return res.json() as Promise<User>
}

// Awaited<T> — recursively unwraps
type R = Awaited<Promise<Promise<number>>>  // number

// Top-level await — module-only
const config = await loadConfig()

Promise.allSettled / .race / .any

const results = await Promise.allSettled([fetchUser("a"), fetchUser("b")])
for (const r of results) {
  if (r.status === "fulfilled") ...r.value
  else ...r.reason
}

Decorators stage 3 · 5.0+ docs ↗

function logged<This, Args extends unknown[], R>(
  target: (this: This, ...args: Args) => R,
  context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => R>
) {
  return function (this: This, ...args: Args): R {
    console.log(`call ${String(context.name)}`, args)
    return target.call(this, ...args)
  }
}

class Api {
  @logged
  fetch(id: string) { ... }
}

Decorator context types

  • ClassDecoratorContext
  • ClassMethodDecoratorContext
  • ClassFieldDecoratorContext
  • ClassGetterDecoratorContext / …Setter…
  • ClassAccessorDecoratorContext
Note: standard decorators (5.0+) are different from the legacy experimentalDecorators form. They aren't compatible — pick one. Standard is the future.

using & resource management 5.2+ docs ↗

class FileHandle {
  constructor(public path: string) {}
  [Symbol.dispose]() {
    console.log(`closing ${this.path}`)
  }
}

function readConfig() {
  using handle = new FileHandle("/etc/app.conf")
  // ... do work ...
  // handle[Symbol.dispose]() runs here automatically
}

// Async variant
await using conn = await openConnection()
// conn[Symbol.asyncDispose]() awaited at scope exit

Helper types

Disposable       // has [Symbol.dispose]()
AsyncDisposable  // has [Symbol.asyncDispose](): Promise<void>

tsconfig essentials docs ↗

{
  "compilerOptions": {
    "target": "ES2024",
    "module": "nodenext",
    "moduleResolution": "nodenext",
    "lib": ["ES2024"],

    "strict": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,
    "noImplicitOverride": true,
    "noFallthroughCasesInSwitch": true,
    "noPropertyAccessFromIndexSignature": true,

    "verbatimModuleSyntax": true,
    "isolatedModules": true,
    "erasableSyntaxOnly": true,     // 5.8+ : forbid enums, namespaces, etc.
    "allowImportingTsExtensions": true,
    "rewriteRelativeImportExtensions": true, // 5.7+ : .ts → .js on emit

    "skipLibCheck": true,
    "noEmit": true
  }
}

Strict family (turned on by "strict": true)

FlagWhat it does
noImplicitAnyerror on inferred any
strictNullChecksnull/undefined not assignable to others
strictFunctionTypescontravariant param check
strictBindCallApplytype-check .bind/.call/.apply
strictPropertyInitializationclass fields must be assigned
alwaysStrictemit "use strict"
useUnknownInCatchVariablescatch (e: unknown)

What's new — 5.x → 6.0 docs ↗

6.0 (current)

  • Deprecation warnings for outFile, module: UMD/AMD/System, moduleResolution: classic, target: ES5. All scheduled for removal in 7.0.
  • Escape hatch: "ignoreDeprecations": "6.0" in tsconfig.
  • Many internal perf wins, faster project loading.

5.8

  • --erasableSyntaxOnly — bans constructs that can't be type-stripped (enums, namespaces, parameter properties).
  • Granular conditional return checks; better narrowing on nested returns.

5.7

  • --rewriteRelativeImportExtensions — emit-time .ts → .js rewriting (lets you import with the source extension).
  • Never-initialized variable diagnostics.
  • Path-based tsconfig.json inheritance via extends.

5.6

  • Disallowed implicit nullish/truthy comparisons (if (regex) → warns).
  • Iterator helper methods typed (.map, .filter on iterators).
  • Strict checks on built-in iterator return types.

5.5

  • Inferred type predicates.filter(x => x != null) now narrows.
  • Regex syntax validation at compile time.
  • --isolatedDeclarations — fast .d.ts emit without full type-check.

5.4

  • NoInfer<T> intrinsic.
  • Preserved narrowing inside closures after last assignment.

5.3

  • Import attributes: import x from "./y" with { type: "json" }.
  • Narrowing using Symbol.hasInstance.

5.2

  • using & await using declarations; Disposable, AsyncDisposable.
  • Decorator metadata (context.metadata).
  • Copying array methods: .toSorted(), .toReversed(), .toSpliced(), .with().

5.1

  • Easier return inference (functions returning undefined).
  • Namespaced JSX attributes.

5.0

  • Stage-3 decorators.
  • const type parameters.
  • All enums become union enums.
  • --moduleResolution bundler.
  • verbatimModuleSyntax.