Skip to content
Laviyx Learn

Try

TypeScript Cheat Sheet

Types, interfaces, unions and narrowing, functions, classes, generics, utility and mapped types, assertions and tsconfig.

Updated

Basic types and annotations#

TypeScript
let title: string = "TypeScript";
let lessons: number = 39;
let published: boolean = true;
let tags: string[] = ["types", "compiler"];
let point: [number, number] = [3, 4];               // tuple
let id: string | number = 42;                       // union
let size: "sm" | "md" | "lg" = "md";                // literal union
let maybe: string | null = null;
let data: unknown = JSON.parse("{}");               // must narrow before use
function log(message: string): void {
  console.log(message);
}
function fail(message: string): never {
  throw new Error(message);
}

Prefer inference for initialised variables; always annotate parameters.

Object types, aliases and interfaces#

TypeScript
type User = {
  readonly id: number;
  name: string;
  email?: string;                 // optional
};

interface Product {
  sku: string;
  price: number;
  describe(): string;
}

interface DigitalProduct extends Product {
  downloadUrl: string;
}

type WithTimestamps = { createdAt: Date };
type StoredUser = User & WithTimestamps;           // intersection

type Prices = Record<string, number>;              // dictionary
type Scores = { [subject: string]: number };       // index signature

Functions#

TypeScript
function greet(name: string, title?: string, greeting = "Hello"): string {
  return title ? `${greeting}, ${title} ${name}` : `${greeting}, ${name}`;
}
const sum = (...nums: number[]): number => nums.reduce((a, b) => a + b, 0);

type Formatter = (value: number) => string;
const rupees: Formatter = (v) => `₹${v.toFixed(2)}`;

function parse(value: string): number;
function parse(value: string[]): number[];
function parse(value: string | string[]): number | number[] {
  return Array.isArray(value) ? value.map(Number) : Number(value);
}

Narrowing#

TypeScript
function describe(value: string | number | Date | null): string {
  if (value === null) return "nothing";
  if (typeof value === "string") return value.toUpperCase();
  if (value instanceof Date) return value.toISOString();
  return value.toFixed(2);
}

type Shape = { kind: "circle"; r: number } | { kind: "square"; side: number };
function area(s: Shape): number {
  switch (s.kind) {
    case "circle":
      return Math.PI * s.r ** 2;
    case "square":
      return s.side ** 2;
    default: {
      const unhandled: never = s;     // exhaustive check
      return unhandled;
    }
  }
}

function isUser(v: unknown): v is { name: string } {
  return typeof v === "object" && v !== null && typeof (v as { name?: unknown }).name === "string";
}

Classes#

TypeScript
class Account {
  #balance = 0;
  static readonly BANK = "Laviyx Bank";

  constructor(public readonly owner: string, private limit = 1000) {}

  deposit(amount: number): void {
    this.#balance += amount;
  }
  get balance(): number {
    return this.#balance;
  }
}

abstract class Shape2 {
  abstract area(): number;
  describe(): string {
    return `area ${this.area()}`;
  }
}

interface Notifier {
  send(to: string): void;
}
class EmailNotifier implements Notifier {
  send(to: string): void {
    console.log(`email to ${to}`);
  }
}

Generics#

TypeScript
function first<T>(items: readonly T[]): T | undefined {
  return items[0];
}
function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}
interface ApiResponse<T = unknown> {
  ok: boolean;
  data: T;
}
class Box<T> {
  constructor(public value: T) {}
}

Type operators and utility types#

TypeScript
const ROUTES = { home: "/", courses: "/courses" } as const;
type RouteName = keyof typeof ROUTES;              // "home" | "courses"
type RoutePath = (typeof ROUTES)[RouteName];       // "/" | "/courses"

interface Person {
  id: number;
  name: string;
  email: string;
}
type Update = Partial<Omit<Person, "id">>;
type Preview = Pick<Person, "id" | "name">;
type Frozen = Readonly<Person>;
type Visible = Exclude<"draft" | "live" | "archived", "draft">;
type Loaded = Awaited<Promise<string[]>>;
type Ret = ReturnType<() => { ok: boolean }>;

type Optional<T> = { [K in keyof T]?: T[K] };                      // mapped type
type ElementOf<T> = T extends (infer E)[] ? E : never;             // conditional + infer
type EventName = `on${Capitalize<"click" | "focus">}`;             // template literal

Assertions#

TypeScript
const input = document.querySelector<HTMLInputElement>("#email");  // generic
const box = document.getElementById("box") as HTMLDivElement | null; // as (unchecked)
const theme = { primary: "#2f5fe0" } satisfies Record<string, string>; // checked, keeps literal info
const value = new Map([["a", 1]]).get("a")!;                          // non-null (unchecked)

Prefer narrowing and satisfies over as and !.

tsconfig essentials#

JSON
{
  "compilerOptions": {
    "target": "es2022",
    "module": "nodenext",
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "verbatimModuleSyntax": true,
    "skipLibCheck": true,
    "outDir": "dist"
  },
  "include": ["src"]
}

npx tsc compiles, npx tsc --noEmit only type-checks, npx tsc --watch recompiles on save.

Want the full explanations? Go to the Learn TypeScript course →