TypeScript Cheat Sheet

TypeScript 5.x reference covering basic types, unions, interfaces, generics, narrowing, utility types, and tsconfig essentials.

Languages
typescript
types
javascript

TypeScript is JavaScript with a structural type system that compiles away. Lean on unknown over any, prefer narrowing to type assertions, and let tsc --strict catch the mistakes that runtime tests usually miss.

Basic types

The primitive types mirror JavaScript: string, number, boolean, plus the structural types array, tuple, object, and the bottom and top helpers never / unknown / any / void.

ts
// Primitives
const name: string = "Ada";
const age: number = 36;
const active: boolean = true;
const big: bigint = 100n;
const sym: symbol = Symbol("id");

// Arrays and tuples
const tags: string[] = ["ts", "js"];
const pair: [string, number] = ["x", 1];

// Special types
let v: any = "anything";           // opt out of type checking — avoid
let u: unknown = fetchJson();       // top type, must narrow before use
function fail(): never { throw new Error("nope"); } // bottom type
function log(): void { console.log("hi"); }         // no return value
Table
TypeUse for
stringText data
numberAll numeric values (no int/float split)
bigintIntegers larger than 2^53 − 1
booleantrue / false
string[] or Array<string>List of strings
[A, B]Fixed-length record with known position types
unknownUntrusted input — narrow before use
anyEscape hatch — turns off the checker
neverFunction that never returns normally
voidFunction returns nothing meaningful

Union and intersection types

A union A | B is "either A or B"; an intersection A & B is "both A and B at once". Reach for unions first — they model real alternatives cleanly with discriminated narrowing.

ts
// Union: one of several shapes
type Id = string | number;
type Result = { ok: true; value: string } | { ok: false; error: Error };

// Intersection: combine capability types
type WithTimestamps = { createdAt: Date; updatedAt: Date };
type User = { id: string; name: string } & WithTimestamps;

Use a discriminant field when union members have a common shape — narrowing becomes exhaustive and safe.

ts
type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "square"; side: number }
  | { kind: "triangle"; base: number; height: number };

function area(s: Shape): number {
  switch (s.kind) {
    case "circle":   return Math.PI * s.radius ** 2;
    case "square":   return s.side ** 2;
    case "triangle": return (s.base * s.height) / 2;
  }
}

Type aliases vs interfaces

Both define object types; the choice mostly comes down to intent. Use interface for public API shapes that callers may extend, and type for unions, intersections, and computed shapes.

ts
interface User {
  readonly id: string;
  name: string;
  email?: string;            // optional
}

type Point = { x: number; y: number };

// Extension with interface
interface AdminUser extends User {
  permissions: string[];
}

// Equivalent with intersection type
type AdminUser2 = User & { permissions: string[] };

// Implementation via declaration merging
interface Window { appVersion: string }
Table
Usetypeinterface
Object shape
Union / intersection
Computed via mapped/conditional types
Declaration merging
implements on classes
Extends via extendsvia intersectionvia extends

Functions and optional parameters

Annotate parameters and return types at the boundaries (public APIs, exported functions) and let inference work inside. Optional parameters go at the end of the parameter list.

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

const greet = (name: string = "World"): string => `Hello, ${name}`;

// Optional with `?`, default with `=`
function load(key: string, opts?: { cache?: boolean }): string {
  return `${key}:${opts?.cache ?? true}`;
}

// Rest parameters must be typed as an array
function tag(prefix: string, ...parts: string[]): string {
  return [prefix, ...parts].join(":");
}
Table
FormBehavior
(a: A, b: B) => RInline function type
function f(a: A): R { ... }Declaration with body
param?: TOptional, may be undefined
param: T = defaultDefault value if omitted
...rest: T[]Rest must be an array type

Generics

Generics parameterize types so the same code works on multiple shapes. Use extends to constrain what the type parameter can be.

ts
function identity<T>(value: T): T {
  return value;
}

// Constrained generic
function longest<T extends { length: number }>(a: T, b: T): T {
  return a.length >= b.length ? a : b;
}

// Generic interface
interface Repository<T> {
  get(id: string): Promise<T | null>;
  save(item: T): Promise<void>;
}

// Multiple type parameters
function zip<A, B>(a: A[], b: B[]): Array<[A, B]> {
  return a.map((x, i) => [x, b[i]]);
}
Table
ConstructExample
Generic functionfunction f<T>(x: T): T
Generic interfaceinterface Box<T> { value: T }
Generic classclass Stack<T> { ... }
Defaultfunction f<T = string>() {}
Constraint<T extends { id: string }>

Narrowing

Narrowing is how TypeScript refines a broad type into a specific one based on runtime checks. It is the alternative to type assertions and almost always the better one.

ts
function print(value: string | number) {
  if (typeof value === "string") {
    console.log(value.toUpperCase()); // value: string
  } else {
    console.log(value.toFixed(2));    // value: number
  }
}

class Dog { bark() {} }
class Cat { meow() {} }
function speak(animal: Dog | Cat) {
  if (animal instanceof Dog) animal.bark();
  else animal.meow();
}

interface ApiOk    { ok: true; data: unknown }
interface ApiFail  { ok: false; error: Error }
function handle(r: ApiOk | ApiFail) {
  if (r.ok) {
    // r.data is reachable here
  } else {
    console.error(r.error);
  }
}
Table
PredicateNarrows by
typeof x === "string"Primitive type
x instanceof CClass identity
"key" in xProperty presence
Array.isArray(x)Array vs non-array
Discriminant equality (r.kind === "x")Tagged union members

Avoid type assertions (value as T) where narrowing works. Assertions are unchecked: if the runtime shape doesn't match T, the error happens at the worst possible moment.

Utility types

The standard library ships a small toolbox of generic types that build new types from existing ones. Reach for them before writing your own mapped type.

ts
interface User {
  id: number;
  name: string;
  email?: string;
}

type UserUpdate  = Partial<User>;                          // all optional
type CreateUser  = Omit<User, "id">;                      // drop a key
type UserPreview = Pick<User, "id" | "name">;             // keep only some
type RequiredUser = Required<User>;                       // all required
type ReadonlyUser = Readonly<User>;                       // no mutation
type UsersById    = Record<string, User>;                 // dictionary shape
type UserValues   = ReturnType<typeof getUser>;           // return type of fn
type Awaited<T>   = T extends Promise<infer U> ? U : T;   // unwrap promise
Table
UtilityEffect
Partial<T>All properties optional
Required<T>All properties required
Readonly<T>All properties read-only
Pick<T, K>Subset of keys
Omit<T, K>All keys except K
Record<K, V>Map of K to V
Exclude<U, X>Remove types from a union
Extract<U, X>Keep only types assignable to X
ReturnType<F>Return type of function F
Parameters<F>Tuple of F's parameter types
Awaited<T>Unwrap Promise<T> recursively

tsconfig.json essentials

tsconfig.json controls how tsc compiles. strict: true enables the family of strictness flags that catch real bugs; everything else is project taste.

json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "lib": ["ES2022", "DOM", "DOM.Iterable"],

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

    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,

    "outDir": "./dist",
    "rootDir": "./src",
    "sourceMap": true,

    "skipLibCheck": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}
Table
OptionEffect
targetECMAScript version emitted
moduleModule system in output (ESNext, CommonJS, NodeNext)
moduleResolutionAlgorithm (Bundler, Node, NodeNext)
libType libraries pulled in (DOM, ES2022, …)
strictEnables strictNullChecks, noImplicitAny, etc.
noUncheckedIndexedAccessarr[i] is T | undefined
outDir / rootDirOutput layout
skipLibCheckSkip type-checking declaration files
esModuleInteropSane default imports from CJS modules

Type assertions exist but narrowing is better. Use as const to widen literals into their precise tuple/primitive types, and satisfies T (TS 4.9+) to check a value conforms to a type without losing its narrow inference.

References