TypeScript cheat sheet

A scannable TypeScript reference: 22 short snippets across 12 topics, each linking back to the lesson it came from.

At a glance

TopicWhat it covers
Basic types and inferenceTypeScript infers types from initial values, so most variables need no annotation. Annotate the boundaries -lesson
Interfaces, type aliases and genericsTypeScript is structurally typed: a value matches a type when it has the required members, no matter where it camelesson
tsconfig and toolingModern bundlers and runners such as esbuild, SWC, Vite and transpile-only ts-node strip types without verifying themlesson
Narrowing, unions and exhaustive checksA discriminated union is the most useful shape in TypeScript: one literal property that tells the compiler whichlesson
Typing functions, callbacks and thisParameters, optional and rest arguments, overloads that model real call shapes, contextual typing for callbacks, andlesson
Utility types, keyof and mapped typesUtility types are pure compile-time transforms: the emitted JavaScript is exactly what you would have written by handlesson
Classes, modifiers and abstract typesAccess modifiers and parameter properties, runtime-private fields, implements versus extends, abstract members, and thelesson
Generic patterns without over-engineeringConstraints and defaults, where type inference actually looks, generic data structures, and the judgement to stoplesson
Typing asynchronous and external dataPromises and Awaited, typed fetch results, AbortSignal, and modelling failure as a value instead of hoping nothinglesson
Runtime validation at the edgesCompilation removes every type, so the only thing that can refuse bad data at runtime is code you actually wrote. Thelesson
Declaration files and third-party typesWriting .d.ts declarations, using @types packages, typing an untyped dependency locally, and publishing types with yourlesson
Migrating a JavaScript project incrementallyThe migration is a sequence of small steps, each of which leaves the build green. Start with allowJs so JavaScriptlesson

Quick snippets

Basic types and inference

Primitives and inference

let title: string = 'Notes';      // explicit annotation
let count = 0;                    // inferred as number
let tags = ['a', 'b'];            // inferred as string[]
let maybe: string | null = null;  // union with null

const mode = 'dark';              // type is the literal "dark"
let loose = 'dark';               // type is string, because let can change

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

… 1 more lines in the full lesson.

Unions and narrowing

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

function render(result: Result) {
  if (result.ok) {
    return result.value;      // narrowed to the success branch
  }
  return result.error;        // narrowed to the failure branch
}

// truthiness is not the same as presence

… 4 more lines in the full lesson.

Full lesson: Basic types and inference →

Interfaces, type aliases and generics

Describing shapes

interface User {
  id: number;
  name: string;
  email?: string;              // optional member
  readonly createdAt: Date;    // cannot be reassigned after construction
}

interface Admin extends User {
  permissions: string[];
}

type Point = { x: number; y: number };          // an object shape

… 3 more lines in the full lesson.

Generics

function first<T>(items: T[]): T | undefined {
  return items[0];
}
const number = first([1, 2, 3]);     // number | undefined

// constraints keep the generic useful instead of powerless
function pluck<T, K extends keyof T>(items: T[], key: K): T[K][] {
  return items.map(item => item[key]);
}
pluck(users, 'id');                  // number[]
// pluck(users, 'nope');             // error: not a key of User

… 7 more lines in the full lesson.

Full lesson: Interfaces, type aliases and generics →

tsconfig and tooling

Where the types actually get checked

# check only - writes no files
npx tsc --noEmit

# build, including .d.ts declaration files
npx tsc

# a bundler strips types without checking them
npx esbuild src/index.ts --bundle --outfile=dist/bundle.js

# so run the checker separately, in CI and in a pre-commit hook
npx tsc --noEmit && npm test

A tsconfig worth starting from

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitOverride": true,
    "declaration": true,
    "sourceMap": true,
    "esModuleInterop": true,
    "skipLibCheck": true,

… 5 more lines in the full lesson.

Full lesson: tsconfig and tooling →

Narrowing, unions and exhaustive checks

Predicates, in and satisfies

// satisfies checks the value against a type but keeps the narrow inferred type
const config = {
  retries: 3,
  endpoint: '/api/v1'
} satisfies Record<string, string | number>;

config.retries.toFixed(0);        // number, not string | number
// config.reteries;               // error: the key does not exist

// an annotation would widen it instead
const widened: Record<string, string | number> = config;
// widened.retries.toFixed(0);    // error: no such property on the wide type

Discriminated unions

type Shape =
  | { kind: 'circle'; radius: number }
  | { kind: 'square'; side: number }
  | { kind: 'rect'; width: number; height: number };

function area(shape: Shape): number {
  switch (shape.kind) {                       // the discriminant drives the narrowing
    case 'circle':
      return Math.PI * shape.radius ** 2;     // only the circle payload exists here
    case 'square':
      return shape.side ** 2;
    case 'rect':

… 11 more lines in the full lesson.

Full lesson: Narrowing, unions and exhaustive checks →

Typing functions, callbacks and this

Signatures, optional and rest parameters

// a reusable function type: write the shape once, use it everywhere
type Mapper = (value: string, index: number) => number;

function repeat(value: string, times = 1, separator?: string): string {
  return Array.from({ length: times }, () => value).join(separator ?? '');
}

// rest parameters collect the remainder into a real array
function sum(...values: number[]): number {
  return values.reduce((total, value) => total + value, 0);
}

… 16 more lines in the full lesson.

The this parameter and detached methods

class Counter {
  count = 0;

  // a fake first parameter: erased at runtime, checked at compile time
  increment(this: Counter, by: number): void {
    this.count += by;
  }
}

const counter = new Counter();
const detach = counter.increment;
// detach(1);                     // error: this would be undefined

… 11 more lines in the full lesson.

Full lesson: Typing functions, callbacks and this →

Utility types, keyof and mapped types

The utility type toolkit

interface Article {
  id: string;
  title: string;
  body: string;
  publishedAt: Date;
}

type Draft = Partial<Article>;                  // every member optional
type Complete = Required<Draft>;                // every member required again
type Card = Pick<Article, 'id' | 'title'>;      // only these two members
type Meta = Omit<Article, 'body'>;              // everything except body
type Headers = Record<string, string>;          // any string key maps to string

… 8 more lines in the full lesson.

keyof, indexed access and mapped types

// K constrained to a key of T ties the value to the field it writes
function set<T, K extends keyof T>(target: T, key: K, value: T[K]): T {
  return { ...target, [key]: value };
}

set(article, 'title', 'Hello');          // ok
// set(article, 'title', 42);            // error: number is not string
// set(article, 'titel', 'x');           // error: not a key of Article

// a mapped type rebuilds a shape member by member
type Nullable<T> = { [K in keyof T]: T[K] | null };

… 10 more lines in the full lesson.

Conditional and template literal types

// a conditional type picks a branch by assignability, with infer to capture a part
type ElementType<T> = T extends (infer U)[] ? U : T;
type Item = ElementType<string[]>;                // string
type Self = ElementType<number>;                  // number

type Unwrap<T> = T extends Promise<infer U> ? Unwrap<U> : T;
type Value = Unwrap<Promise<Promise<boolean>>>;   // boolean

// template literal types build string unions out of other unions
type EventName = 'click' | 'focus' | 'blur';
type HandlerName = `on${Capitalize<EventName>}`;   // 'onClick' | 'onFocus' | 'onBlur'

… 10 more lines in the full lesson.

Full lesson: Utility types, keyof and mapped types →

Classes, modifiers and abstract types

Modifiers and parameter properties

class Invoice {
  // parameter properties declare and assign in one line
  constructor(
    public readonly id: string,
    private items: number[] = [],
    protected currency: string = 'USD'
  ) {}

  #revision = 0;                  // a real runtime-private field

  add(amount: number): void {
    this.items.push(amount);

… 16 more lines in the full lesson.

Full lesson: Classes, modifiers and abstract types →

Generic patterns without over-engineering

Where inference actually looks

// inference reads the arguments, so callers usually pass no type arguments
function pair<A, B>(a: A, b: B): [A, B] {
  return [a, b];
}
const point = pair(1, 'x');        // [number, string]

// a type parameter that appears only in the return position has nothing to read
function empty<T>(): T[] { return []; }
// const bad = empty();            // unknown[]
const good = empty<string>();      // string[]

// a callback parameter is inferred contextually from the other argument

… 16 more lines in the full lesson.

Knowing when to stop

// T appears once, so it links nothing and adds only noise
function logValue<T>(value: T): void {
  console.log(value);                 // unknown would do exactly the same job
}

// two parameters with a relationship are worth one type parameter
function firstOr<T>(items: T[], fallback: T): T {
  return items.length > 0 ? items[0] : fallback;
}
const count = firstOr([1, 2, 3], 0);       // number, not number | string

// a named helper type keeps a signature readable

… 11 more lines in the full lesson.

Full lesson: Generic patterns without over-engineering →

Typing asynchronous and external data

Promises and Awaited

interface User { id: string; name: string }

// an async function always returns a promise, so the annotation is the resolved value
async function loadUser(id: string): Promise<User> {
  const response = await fetch('/api/users/' + id);
  return (await response.json()) as User;
}

// Awaited unwraps nested promises, which a hand-written Promise<T> cannot
type Nested = Awaited<Promise<Promise<number>>>;              // number
type Resolved = Awaited<ReturnType<typeof loadUser>>;         // User

… 15 more lines in the full lesson.

Modelling failure without exceptions

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

// one wrapper turns any rejection into a value the caller must inspect
async function attempt<T>(work: () => Promise<T>): Promise<Result<T>> {
  try {
    return { ok: true, value: await work() };
  } catch (error) {
    return { ok: false, error: error instanceof Error ? error : new Error(String(error)) };
  }
}

… 15 more lines in the full lesson.

Full lesson: Typing asynchronous and external data →

Runtime validation at the edges

Types disappear at runtime

// each of these compiles, and none of them is checked at runtime
const user = JSON.parse(raw) as User;                 // a claim the compiler believes
const env = process.env as Record<string, string>;    // missing keys are undefined
declare const settings: Settings;                     // trust me - no code is emitted

// the honest version: accept unknown, then prove the shape
function isUser(value: unknown): value is User {
  if (typeof value !== 'object' || value === null) return false;
  const candidate = value as { id?: unknown; name?: unknown };
  return typeof candidate.id === 'string' && typeof candidate.name === 'string';
}

… 9 more lines in the full lesson.

Full lesson: Runtime validation at the edges →

Declaration files and third-party types

Publishing types with a package

// emit declarations alongside the JavaScript
// tsc --declaration --emitDeclarationOnly --outDir dist

// one entry point re-exports the public surface
export { loadUser, type User } from './user.js';
export { parsePort } from './port.js';

// an explicit public API keeps internals unimportable, so refactors
// inside the package are not breaking changes for consumers

// verify the shipped types resolve the way consumers will resolve them
// npx @arethetypeswrong/cli --pack .

Types for someone else's package

// src/types/legacy-chart.d.ts - type an untyped dependency locally,
// without waiting for an upstream fix or publishing a fork

declare module 'legacy-chart' {
  export interface Options {
    width: number;
    height: number;
    theme?: 'light' | 'dark';
  }
  export function render(target: string, options: Options): void;
}

… 8 more lines in the full lesson.

Full lesson: Declaration files and third-party types →

Migrating a JavaScript project incrementally

Suppressions as tracked debt

// a suppression is a debt note: it says "known, not fixed"
const payload = JSON.parse(raw) as any;        // bad: any leaks into every caller

// acceptable: constrain the escape hatch to one expression, with a reason
// @ts-expect-error third-party types are wrong here; upstream issue #412
legacyClient.send(payload);

// replace a real any with unknown as soon as the callers can handle it
function parse(raw: string): unknown { return JSON.parse(raw); }

// count the debt so it cannot grow unnoticed
// grep -rn "@ts-expect-error" src | wc -l

… 2 more lines in the full lesson.

Full lesson: Migrating a JavaScript project incrementally →

FAQ

Is this TypeScript cheat sheet free to use?
Yes. No sign-up and no tracking: the page is static, every example is on the page itself, and you can print it or save it as a one-page reference.
Where do the examples come from?
Every snippet is taken from the 12 lessons of the TypeScript course on this site, and each section links back to the lesson it was pulled from.
How do I go deeper than a cheat sheet?
Open the full TypeScript course — it carries the worked explanations, the edge cases and the exercises behind every line here.

HTML CSS JavaScript HTML DOM AJAX JSON

Last refreshed 2026-09-27.