Toolmingo
Guides12 min read

TypeScript Cheat Sheet: Types, Generics, and Patterns

A complete TypeScript cheat sheet — primitive types, interfaces, generics, utility types, enums, type guards, decorators, and common patterns. Copy-ready examples.

TypeScript adds static typing to JavaScript — which means catching bugs before runtime. This reference covers every construct you need, from basic types to advanced generic patterns.

Quick reference

The 25 patterns that cover 90% of everyday TypeScript.

Pattern What it does
let x: number = 5 Primitive type annotation
const s: string = "hi" String annotation
let b: boolean = true Boolean annotation
let n: null = null Null type
let u: undefined Undefined type
let a: number[] = [1,2] Array type
let t: [string, number] Tuple type
let o: Record<string, number> Object with typed values
interface Foo { x: number } Interface declaration
type Bar = { x: number } Type alias
type AB = A | B Union type
type AB = A & B Intersection type
function f<T>(x: T): T Generic function
Partial<T> All fields optional
Required<T> All fields required
Readonly<T> All fields readonly
Pick<T, 'a'|'b'> Subset of fields
Omit<T, 'a'> Exclude fields
ReturnType<typeof f> Infer return type
keyof T Union of keys
x is Dog Type predicate (type guard)
as const Literal type narrowing
x! Non-null assertion
x as string Type assertion
satisfies T Validate without widening

Primitive types

// Basic types
let age: number = 30;
let name: string = "Alice";
let active: boolean = true;
let nothing: null = null;
let missing: undefined = undefined;
let any: any = "anything goes";         // avoid — disables type checking
let val: unknown = fetchData();         // prefer unknown over any

// any vs unknown: unknown requires a type check before use
function handleInput(input: unknown) {
  if (typeof input === "string") {
    console.log(input.toUpperCase());   // OK — narrowed to string
  }
}

// never — functions that never return
function fail(msg: string): never {
  throw new Error(msg);
}

// void — functions that return undefined
function log(msg: string): void {
  console.log(msg);
}

// BigInt and Symbol
let big: bigint = 9007199254740991n;
let sym: symbol = Symbol("id");

// Literal types — restrict to exact values
let direction: "north" | "south" | "east" | "west" = "north";
let statusCode: 200 | 404 | 500 = 200;

Arrays, tuples, and objects

// Array — two equivalent syntaxes
let nums: number[] = [1, 2, 3];
let strs: Array<string> = ["a", "b"];

// Readonly array — prevent mutation
const items: readonly number[] = [1, 2, 3];
// items.push(4);  ← Error: no push on readonly array

// Tuple — fixed-length array with known types at each position
let pair: [string, number] = ["Alice", 30];
let [person, age] = pair;              // destructuring

// Labeled tuple (TS 4.0+)
type Point = [x: number, y: number];
let p: Point = [3, 7];

// Object types
let user: { name: string; age: number } = { name: "Alice", age: 30 };

// Optional fields
let config: { host: string; port?: number } = { host: "localhost" };

// Index signatures — objects with dynamic keys
let scores: { [key: string]: number } = {};
scores["Alice"] = 10;

// Record<K, V> — cleaner index signature
let lookup: Record<string, number> = { a: 1, b: 2 };

Interfaces

interface User {
  id: number;
  name: string;
  email?: string;             // optional
  readonly createdAt: Date;   // read-only after creation
}

// Method signatures
interface Greeter {
  greet(name: string): string;
  greet(name: string, greeting: string): string;  // overload
}

// Extending interfaces
interface Admin extends User {
  role: "admin" | "superadmin";
  permissions: string[];
}

// Extending multiple interfaces
interface SuperAdmin extends Admin, Auditable {
  auditLog: string[];
}

// Interface vs type alias:
// - interface: can be reopened/merged, better for OOP/classes
// - type: can use unions/intersections, better for complex types

Type aliases and union/intersection types

// Type alias
type ID = string | number;
type Callback = (event: MouseEvent) => void;

// Union — one of several types
type Result<T> = { data: T; error: null } | { data: null; error: Error };

// Discriminated union — each branch has a literal tag field
type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "rect"; width: number; height: number };

function area(s: Shape): number {
  switch (s.kind) {
    case "circle": return Math.PI * s.radius ** 2;
    case "rect":   return s.width * s.height;
  }
}

// Intersection — combine all fields
type Named = { name: string };
type Aged = { age: number };
type Person = Named & Aged;   // { name: string; age: number }

// Type narrowing with typeof
function format(x: string | number): string {
  if (typeof x === "string") return x.toUpperCase();
  return x.toFixed(2);
}

Functions

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

// Arrow function
const multiply = (a: number, b: number): number => a * b;

// Optional and default parameters
function greet(name: string, greeting = "Hello"): string {
  return `${greeting}, ${name}!`;
}

// Rest parameters
function sum(...nums: number[]): number {
  return nums.reduce((a, b) => a + b, 0);
}

// Overloads — different call signatures
function stringify(x: number): string;
function stringify(x: boolean): string;
function stringify(x: number | boolean): string {
  return String(x);
}

// Function type
type Transformer<T, U> = (input: T) => U;
const toStr: Transformer<number, string> = (n) => String(n);

Generics

// Generic function
function identity<T>(value: T): T {
  return value;
}
identity<string>("hello");   // explicit
identity(42);                // inferred

// Generic interface
interface Repository<T> {
  findById(id: string): Promise<T>;
  save(entity: T): Promise<void>;
  delete(id: string): Promise<void>;
}

// Generic class
class Stack<T> {
  private items: T[] = [];
  push(item: T): void { this.items.push(item); }
  pop(): T | undefined { return this.items.pop(); }
  peek(): T | undefined { return this.items[this.items.length - 1]; }
}

// Constraints — T must have certain shape
function getLength<T extends { length: number }>(x: T): number {
  return x.length;
}
getLength("hello");   // 5
getLength([1, 2, 3]); // 3

// keyof constraint — only valid keys
function getField<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

// Default generic parameter (TS 2.3+)
interface ApiResponse<T = unknown> {
  data: T;
  status: number;
}

Utility types

interface User {
  id: number;
  name: string;
  email: string;
  password: string;
}

// Partial<T> — make all fields optional
type UserInput = Partial<User>;
// { id?: number; name?: string; email?: string; password?: string }

// Required<T> — make all fields required
type StrictUser = Required<UserInput>;

// Readonly<T> — prevent mutation
type ImmutableUser = Readonly<User>;
// const u: ImmutableUser = {...}; u.name = "x"; ← Error

// Pick<T, K> — select a subset of fields
type PublicUser = Pick<User, "id" | "name" | "email">;

// Omit<T, K> — exclude fields
type SafeUser = Omit<User, "password">;

// Exclude<T, U> — remove from union
type NumOrStr = number | string | boolean;
type NumOrBool = Exclude<NumOrStr, string>;  // number | boolean

// Extract<T, U> — keep only matching union members
type OnlyStr = Extract<NumOrStr, string>;    // string

// NonNullable<T> — remove null and undefined
type Value = NonNullable<string | null | undefined>;  // string

// ReturnType<T> — infer function return type
function fetchUser() { return { id: 1, name: "Alice" }; }
type FetchedUser = ReturnType<typeof fetchUser>;  // { id: number; name: string }

// Parameters<T> — infer parameter tuple
type FetchParams = Parameters<typeof fetchUser>;  // []

// Awaited<T> — unwrap Promise (TS 4.5+)
type Resolved = Awaited<Promise<string>>;  // string

// Record<K, V> — typed object
type PageMap = Record<"home" | "about" | "contact", string>;

Enums

// Numeric enum (default, starts at 0)
enum Direction {
  North,   // 0
  South,   // 1
  East,    // 2
  West,    // 3
}
let dir: Direction = Direction.North;

// String enum — preferred, values are meaningful
enum Status {
  Active = "ACTIVE",
  Inactive = "INACTIVE",
  Pending = "PENDING",
}

// Const enum — inlined at compile time (no runtime object)
const enum Color {
  Red = 0,
  Green = 1,
  Blue = 2,
}

// Better alternative to enums: const object + type
const DIRECTION = {
  North: "north",
  South: "south",
} as const;
type DirectionType = typeof DIRECTION[keyof typeof DIRECTION];
// "north" | "south"

Type guards and narrowing

// typeof guard
function process(x: string | number) {
  if (typeof x === "string") {
    return x.trim();     // x is string here
  }
  return x.toFixed(2);  // x is number here
}

// instanceof guard
function handle(err: Error | string) {
  if (err instanceof Error) {
    return err.message;  // Error
  }
  return err;            // string
}

// in guard — check for property existence
type Cat = { meow(): void };
type Dog = { bark(): void };
function makeNoise(animal: Cat | Dog) {
  if ("meow" in animal) {
    animal.meow();
  } else {
    animal.bark();
  }
}

// User-defined type predicate
function isString(x: unknown): x is string {
  return typeof x === "string";
}

// Discriminated union narrowing
type Result<T> =
  | { ok: true; value: T }
  | { ok: false; error: string };

function unwrap<T>(r: Result<T>): T {
  if (r.ok) return r.value;
  throw new Error(r.error);
}

// Assertion function (TS 3.7+)
function assert(condition: boolean, msg: string): asserts condition {
  if (!condition) throw new Error(msg);
}

Classes

class Animal {
  // Fields
  readonly species: string;
  protected name: string;
  private #age: number;     // private field (hard private, ES2022)

  constructor(species: string, name: string, age: number) {
    this.species = species;
    this.name = name;
    this.#age = age;
  }

  // Getter/setter
  get age(): number { return this.#age; }
  set age(v: number) {
    if (v < 0) throw new Error("negative age");
    this.#age = v;
  }

  // Method
  describe(): string {
    return `${this.name} (${this.species})`;
  }

  // Static
  static create(species: string, name: string): Animal {
    return new Animal(species, name, 0);
  }
}

class Dog extends Animal {
  constructor(name: string) {
    super("Canis lupus", name, 0);
  }

  // Override
  describe(): string {
    return `Dog: ${super.describe()}`;
  }
}

// Implementing an interface
interface Serializable {
  serialize(): string;
}

class User implements Serializable {
  constructor(public id: number, public name: string) {}
  serialize() { return JSON.stringify(this); }
}

// Abstract class
abstract class Shape {
  abstract area(): number;
  toString() { return `Area: ${this.area()}`; }
}

Mapped types and conditional types

// Mapped type — transform all properties
type Optional<T> = {
  [K in keyof T]?: T[K];
};

// Mapped type with remapping (TS 4.1+)
type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

// Conditional type
type IsString<T> = T extends string ? true : false;
type R1 = IsString<"hello">;  // true
type R2 = IsString<number>;   // false

// Infer in conditional types
type ElementType<T> = T extends (infer E)[] ? E : never;
type Item = ElementType<string[]>;  // string

// Distributive conditional types
type Flatten<T> = T extends any[] ? T[number] : T;
type F1 = Flatten<number[]>;   // number
type F2 = Flatten<string>;     // string

// Template literal types (TS 4.1+)
type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickEvent = EventName<"click">;  // "onClick"

Common patterns

// Builder pattern
class QueryBuilder {
  private query = "";

  select(fields: string[]): this {
    this.query += `SELECT ${fields.join(", ")} `;
    return this;
  }
  from(table: string): this {
    this.query += `FROM ${table} `;
    return this;
  }
  where(condition: string): this {
    this.query += `WHERE ${condition} `;
    return this;
  }
  build(): string { return this.query.trim(); }
}

const q = new QueryBuilder()
  .select(["id", "name"])
  .from("users")
  .where("active = true")
  .build();

// Branded types — prevent mixing similar primitives
type UserId = string & { readonly _brand: "UserId" };
type OrderId = string & { readonly _brand: "OrderId" };

function makeUserId(id: string): UserId { return id as UserId; }
// function takesUser(id: UserId) {}
// takesUser("raw-string");  ← Error: string is not UserId

// satisfies operator (TS 4.9+) — validate shape without widening
const palette = {
  red: [255, 0, 0],
  green: "#00ff00",
} satisfies Record<string, string | number[]>;

palette.red.map(v => v * 2);   // OK — type preserved as number[]

6 common mistakes

1. Using any instead of unknown

// BAD — disables type checking
function parse(input: any) { return input.toUpperCase(); }  // no error even if input is number

// GOOD — forces type narrowing
function parse(input: unknown) {
  if (typeof input !== "string") throw new Error("expected string");
  return input.toUpperCase();
}

2. Type assertion instead of type guard

// BAD — bypasses type safety
const user = getData() as User;  // crashes if getData() returns null

// GOOD — check first
function isUser(x: unknown): x is User {
  return typeof x === "object" && x !== null && "id" in x;
}
if (isUser(data)) { /* safe */ }

3. Forgetting readonly on tuples

// BAD — tuple can be mutated
function min(...nums: number[]) {
  return nums.sort()[0];  // mutates original array!
}

// GOOD — readonly prevents mutation
function min(...nums: readonly number[]) {
  return [...nums].sort()[0];
}

4. enum runtime overhead

// BAD — generates runtime object, easy to misuse
enum Status { Active = "ACTIVE" }

// GOOD — zero runtime cost, better inference
const Status = { Active: "ACTIVE" } as const;
type Status = typeof Status[keyof typeof Status];

5. Ignoring strict mode

// tsconfig.json — always enable strict
{
  "compilerOptions": {
    "strict": true,          // enables all strict checks
    "noUncheckedIndexedAccess": true,  // arr[0] is T | undefined
    "exactOptionalPropertyTypes": true  // undefined !== missing
  }
}

6. Not using satisfies for config objects

// BAD — type widened, loses specific types
const config: Record<string, unknown> = {
  port: 3000,
  host: "localhost",
};
config.port  // unknown — lost number info

// GOOD — validated but types preserved
const config = {
  port: 3000,
  host: "localhost",
} satisfies Record<string, unknown>;
config.port  // number — preserved!

6 frequently asked questions

What's the difference between interface and type? Use interface when defining object shapes (especially for OOP/classes) — it can be reopened and merged across declaration. Use type when you need unions, intersections, or mapped types. In practice, either works for most cases; pick one and be consistent.

When should I use unknown vs any? Always prefer unknown. Both accept any value, but unknown requires you to narrow the type before using it (with typeof, instanceof, or a type guard). any silently bypasses the type system. Use any only as a last resort, e.g., migrating JavaScript.

What does as const do? It converts an object or array to a deeply readonly literal type. const colors = ["red", "green"] as const makes colors readonly ["red", "green"] instead of string[]. This lets you derive union types from arrays: type Color = typeof colors[number]"red" | "green".

How do I type an async function? The return type is Promise<T> where T is the resolved value:

async function fetchUser(id: number): Promise<User> {
  const res = await fetch(`/api/users/${id}`);
  return res.json() as Promise<User>;
}

What is keyof used for? keyof T produces a union of all keys of type T as string literals. It's most useful in generic functions to ensure you're accessing valid keys:

function get<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}
get(user, "name");   // OK
get(user, "foo");    // Error: "foo" is not a key of User

How do I extend a third-party module's types? Use declaration merging in a .d.ts file:

// types/express.d.ts
declare module "express-serve-static-core" {
  interface Request {
    user?: User;  // add user field to every Request
  }
}

Related tools

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools