TypeScript Cheat Sheet

Quick reference for TypeScript syntax, types, generics, utility types, and common patterns with practical code examples.

Basic Types

Type Description Example
string Text values let name: string = "Alice";
number All numbers (int, float) let age: number = 30;
boolean true / false let done: boolean = false;
null Intentional absence let n: null = null;
undefined Uninitialized value let u: undefined = undefined;
bigint Large integers let big: bigint = 100n;
symbol Unique identifiers let id: symbol = Symbol("id");
any Opt out of type checking let val: any = "anything";
unknown Type-safe any (must narrow) let val: unknown = getData();
void No return value function log(): void {}
never Never returns (throws/infinite) function fail(): never { throw Error(); }

Arrays and Tuples

// Arrays
let nums: number[] = [1, 2, 3];
let strs: Array<string> = ["a", "b", "c"];

// Tuples — fixed-length, typed arrays
let pair: [string, number] = ["age", 30];
let triple: [number, string, boolean] = [1, "yes", true];

// Readonly tuple
let point: readonly [number, number] = [10, 20];

// Named tuple elements (TypeScript 4.0+)
type Range = [start: number, end: number];

// Rest elements in tuples
type StringAndNumbers = [string, ...number[]];
let data: StringAndNumbers = ["label", 1, 2, 3];

Special types

// Object (non-primitive)
let obj: object = { key: "value" };

// Literal types
let direction: "north" | "south" | "east" | "west" = "north";
let httpStatus: 200 | 404 | 500 = 200;

// Type assertions
let input = document.getElementById("name") as HTMLInputElement;
let input2 = <HTMLInputElement>document.getElementById("name");

// Non-null assertion
let el = document.getElementById("app")!; // assert non-null

Interfaces

// Basic interface
interface User {
  id: number;
  name: string;
  email: string;
  age?: number;                // optional property
  readonly createdAt: Date;    // cannot be reassigned
}

// Function type
interface SearchFn {
  (query: string, limit?: number): Promise<User[]>;
}

// Index signature
interface StringMap {
  [key: string]: string;
}

// Extending interfaces
interface Employee extends User {
  department: string;
  salary: number;
}

// Multiple inheritance
interface Manager extends Employee {
  reports: Employee[];
}

// Declaration merging — same name = auto-merged
interface Config {
  apiUrl: string;
}
interface Config {
  timeout: number;
}
// Config now has both apiUrl and timeout

// Implementing interfaces in classes
class UserService implements SearchFn {
  async (query: string, limit?: number): Promise<User[]> {
    // implementation
    return [];
  }
}

Type Aliases

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

// Union type
type ID = string | number;

// Intersection type
type Admin = User & { permissions: string[] };

// Function type
type Handler = (event: Event) => void;

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

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

// Template literal type
type EventName = `on${Capitalize<string>}`;

// Extending with intersection
type Base = { id: number };
type WithTimestamps = Base & {
  createdAt: Date;
  updatedAt: Date;
};

Interface vs Type Alias

Feature Interface Type Alias
Object shapes Yes Yes
Extends / inheritance extends & intersection
Declaration merging Yes No
Unions No Yes
Tuples / primitives No Yes
Mapped types No Yes
Class implements Yes Yes (object types only)

Generics

// Generic function
function identity<T>(value: T): T {
  return value;
}
let num = identity<number>(42);     // explicit
let str = identity("hello");          // inferred as string

// Generic with constraint
function getLength<T extends { length: number }>(item: T): number {
  return item.length;
}
getLength("hello");    // 5
getLength([1, 2, 3]);  // 3

// Multiple type parameters
function pair<K, V>(key: K, value: V): [K, V] {
  return [key, value];
}

// Generic interface
interface ApiResponse<T> {
  data: T;
  status: number;
  message: string;
}

type UserResponse = ApiResponse<User>;
type UsersResponse = ApiResponse<User[]>;

// 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];
  }
}

// Default type parameter
interface PaginatedResult<T = any> {
  items: T[];
  total: number;
  page: number;
  perPage: number;
}

// Generic constraint with keyof
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const user = { name: "Alice", age: 30 };
getProperty(user, "name"); // string
// getProperty(user, "foo"); // Error: "foo" is not in keyof User

Utility Types

Utility Type Description Example
Partial<T> All properties optional Partial<User>
Required<T> All properties required Required<User>
Readonly<T> All properties readonly Readonly<User>
Pick<T, K> Select specific properties Pick<User, "id" | "name">
Omit<T, K> Remove specific properties Omit<User, "password">
Record<K, V> Object with keys K, values V Record<string, number>
Exclude<T, U> Remove types from union Exclude<string | number, string>
Extract<T, U> Keep types from union Extract<string | number, string>
NonNullable<T> Remove null and undefined NonNullable<string | null>
ReturnType<T> Function return type ReturnType<typeof fn>
Parameters<T> Function parameter types Parameters<typeof fn>
Awaited<T> Unwrap Promise type Awaited<Promise<string>>

Utility Types in Action

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

// Partial — for update operations
function updateUser(id: number, updates: Partial<User>): void {
  // updates can have any subset of User properties
}
updateUser(1, { name: "Bob" }); // valid

// Pick — select specific fields
type UserPreview = Pick<User, "id" | "name">;
// { id: number; name: string }

// Omit — exclude sensitive fields
type PublicUser = Omit<User, "password">;
// { id: number; name: string; email: string }

// Record — typed dictionaries
type RolePermissions = Record<"admin" | "user" | "guest", string[]>;
const perms: RolePermissions = {
  admin: ["read", "write", "delete"],
  user: ["read", "write"],
  guest: ["read"],
};

// Readonly — immutable data
const config: Readonly<{ apiUrl: string; timeout: number }> = {
  apiUrl: "https://api.example.com",
  timeout: 5000,
};
// config.apiUrl = "..."; // Error: Cannot assign to readonly property

// ReturnType — extract function return type
function createUser(name: string, email: string) {
  return { id: Date.now(), name, email, createdAt: new Date() };
}
type NewUser = ReturnType<typeof createUser>;
// { id: number; name: string; email: string; createdAt: Date }

Type Guards

// typeof guard
function process(value: string | number) {
  if (typeof value === "string") {
    return value.toUpperCase(); // string methods available
  }
  return value.toFixed(2);      // number methods available
}

// instanceof guard
function handleError(error: Error | string) {
  if (error instanceof Error) {
    console.log(error.message);  // Error properties available
  } else {
    console.log(error);          // narrowed to string
  }
}

// in operator guard
interface Dog { bark(): void; }
interface Cat { meow(): void; }

function speak(pet: Dog | Cat) {
  if ("bark" in pet) {
    pet.bark();  // narrowed to Dog
  } else {
    pet.meow();  // narrowed to Cat
  }
}

// Custom type guard (type predicate)
interface Fish { swim(): void; }
interface Bird { fly(): void; }

function isFish(pet: Fish | Bird): pet is Fish {
  return (pet as Fish).swim !== undefined;
}

function move(pet: Fish | Bird) {
  if (isFish(pet)) {
    pet.swim();  // narrowed to Fish
  } else {
    pet.fly();   // narrowed to Bird
  }
}

// Discriminated union guard
type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "rect"; width: number; height: number }
  | { kind: "triangle"; base: number; height: number };

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

// Exhaustiveness checking with never
function assertNever(x: never): never {
  throw new Error(`Unexpected value: ${x}`);
}

function areaExhaustive(shape: Shape): number {
  switch (shape.kind) {
    case "circle":   return Math.PI * shape.radius ** 2;
    case "rect":     return shape.width * shape.height;
    case "triangle": return (shape.base * shape.height) / 2;
    default:         return assertNever(shape); // compile error if a case is missing
  }
}

Enums

// Numeric enum (auto-increments from 0)
enum Direction {
  Up,      // 0
  Down,    // 1
  Left,    // 2
  Right,   // 3
}

// Numeric enum with explicit values
enum HttpStatus {
  OK = 200,
  NotFound = 404,
  ServerError = 500,
}

// String enum
enum Color {
  Red = "RED",
  Green = "GREEN",
  Blue = "BLUE",
}

// Const enum (inlined at compile time, no runtime object)
const enum LogLevel {
  Debug = "DEBUG",
  Info = "INFO",
  Warn = "WARN",
  Error = "ERROR",
}

// Usage
let status: HttpStatus = HttpStatus.OK;
let dir: Direction = Direction.Up;

// Reverse mapping (numeric enums only)
let name: string = Direction[0]; // "Up"

// Enum as a type
function move(direction: Direction): void {
  // direction is restricted to Direction values
}

// Alternative: const object (often preferred)
const STATUS = {
  Active: "active",
  Inactive: "inactive",
  Pending: "pending",
} as const;

type Status = typeof STATUS[keyof typeof STATUS];
// "active" | "inactive" | "pending"

Union and Intersection Types

// Union types — value is ONE of the types
type StringOrNumber = string | number;
type Result = Success | Error;

// Discriminated unions — tagged union pattern
type ApiResult<T> =
  | { status: "success"; data: T }
  | { status: "error"; error: string }
  | { status: "loading" };

function handleResult(result: ApiResult<User>) {
  switch (result.status) {
    case "success":
      console.log(result.data);   // data available
      break;
    case "error":
      console.log(result.error);  // error available
      break;
    case "loading":
      console.log("Loading...");
      break;
  }
}

// Intersection types — combine ALL types
type Timestamped = {
  createdAt: Date;
  updatedAt: Date;
};

type SoftDeletable = {
  deletedAt: Date | null;
};

type Entity = { id: number };

// Combines all three
type AuditableEntity = Entity & Timestamped & SoftDeletable;

// Intersection with interfaces
interface Printable { print(): void; }
interface Loggable  { log(): void; }

type PrintableAndLoggable = Printable & Loggable;

// Narrowing union types
function format(value: string | number | boolean): string {
  if (typeof value === "string") return value.trim();
  if (typeof value === "number") return value.toFixed(2);
  return value ? "yes" : "no";
}

Template Literal Types

// Basic template literal types
type Greeting = `Hello, ${string}`;
let g: Greeting = "Hello, world";  // OK
// let g2: Greeting = "Hi, world"; // Error

// Event handler names
type EventName = "click" | "scroll" | "mousemove";
type EventHandler = `on${Capitalize<EventName>}`;
// "onClick" | "onScroll" | "onMousemove"

// CSS units
type CSSUnit = "px" | "em" | "rem" | "%";
type CSSValue = `${number}${CSSUnit}`;
let width: CSSValue = "100px";   // OK
let height: CSSValue = "2.5rem"; // OK

// Intrinsic string manipulation types
type Upper = Uppercase<"hello">;       // "HELLO"
type Lower = Lowercase<"HELLO">;       // "hello"
type Cap   = Capitalize<"hello">;      // "Hello"
type Uncap = Uncapitalize<"Hello">;    // "hello"

// Getter/setter pattern
type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

interface Person { name: string; age: number; }
type PersonGetters = Getters<Person>;
// { getName: () => string; getAge: () => number; }

// Key remapping with template literals
type Prefixed<T, P extends string> = {
  [K in keyof T as `${P}_${string & K}`]: T[K];
};

type PrefixedUser = Prefixed<{ name: string; age: number }, "user">;
// { user_name: string; user_age: number }

Decorators

Note: Stage 3 decorators (TypeScript 5.0+) are the standard. Enable with "experimentalDecorators": false (default) or use legacy decorators with "experimentalDecorators": true.

// Class decorator (TC39 Stage 3)
function sealed(constructor: Function) {
  Object.seal(constructor);
  Object.seal(constructor.prototype);
}

@sealed
class BankAccount {
  balance: number = 0;
}

// Method decorator (legacy — experimentalDecorators: true)
function log(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  const original = descriptor.value;
  descriptor.value = function (...args: any[]) {
    console.log(`Calling ${propertyKey} with`, args);
    const result = original.apply(this, args);
    console.log(`Result:`, result);
    return result;
  };
}

class Calculator {
  @log
  add(a: number, b: number): number {
    return a + b;
  }
}

// Property decorator
function validate(target: any, propertyKey: string) {
  let value: any;
  Object.defineProperty(target, propertyKey, {
    get: () => value,
    set: (newVal) => {
      if (typeof newVal !== "string" || newVal.length === 0) {
        throw new Error(`${propertyKey} must be a non-empty string`);
      }
      value = newVal;
    },
  });
}

class User {
  @validate
  name!: string;
}

tsconfig.json Options

Option Description Recommended
target JS version output (ES5, ES2020, ESNext) "ES2022"
module Module system (CommonJS, ESNext, NodeNext) "NodeNext"
moduleResolution How modules are resolved "NodeNext"
strict Enable all strict checks true
strictNullChecks null/undefined are distinct types true (via strict)
noImplicitAny Error on implicit any true (via strict)
esModuleInterop Better CJS/ESM interop true
forceConsistentCasingInFileNames Case-sensitive imports true
skipLibCheck Skip .d.ts checking true
outDir Output directory for compiled files "./dist"
rootDir Root directory of source files "./src"
declaration Generate .d.ts type files true (libraries)
resolveJsonModule Allow importing .json files true
isolatedModules Ensure each file is a module true

Recommended tsconfig.json

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "lib": ["ES2022"],
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "skipLibCheck": true,
    "declaration": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

Common Patterns

Builder Pattern

class QueryBuilder<T> {
  private filters: Partial<T> = {};
  private sortField?: keyof T;
  private limitVal?: number;

  where<K extends keyof T>(field: K, value: T[K]): this {
    this.filters[field] = value;
    return this;
  }

  orderBy(field: keyof T): this {
    this.sortField = field;
    return this;
  }

  limit(n: number): this {
    this.limitVal = n;
    return this;
  }

  build(): { filters: Partial<T>; sort?: keyof T; limit?: number } {
    return { filters: this.filters, sort: this.sortField, limit: this.limitVal };
  }
}

// Usage
const query = new QueryBuilder<User>()
  .where("name", "Alice")
  .orderBy("createdAt")
  .limit(10)
  .build();

Result Type (Error Handling)

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

function divide(a: number, b: number): Result<number, string> {
  if (b === 0) return { ok: false, error: "Division by zero" };
  return { ok: true, value: a / b };
}

const result = divide(10, 3);
if (result.ok) {
  console.log(result.value);  // number, safely narrowed
} else {
  console.log(result.error);  // string, safely narrowed
}

Branded / Opaque Types

// Prevent accidental mixing of structurally identical types
type Brand<T, B extends string> = T & { readonly __brand: B };

type USD = Brand<number, "USD">;
type EUR = Brand<number, "EUR">;

function usd(amount: number): USD { return amount as USD; }
function eur(amount: number): EUR { return amount as EUR; }

function addUSD(a: USD, b: USD): USD {
  return (a + b) as USD;
}

const price = usd(9.99);
const tax = usd(0.80);
addUSD(price, tax);          // OK
// addUSD(price, eur(1.00)); // Error: EUR is not assignable to USD

Mapped Types

// Make all properties optional
type MyPartial<T> = {
  [K in keyof T]?: T[K];
};

// Make all properties nullable
type Nullable<T> = {
  [K in keyof T]: T[K] | null;
};

// Deep readonly
type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object
    ? DeepReadonly<T[K]>
    : T[K];
};

// Remove readonly from all properties
type Mutable<T> = {
  -readonly [K in keyof T]: T[K];
};

// Filter keys by value type
type StringKeys<T> = {
  [K in keyof T]: T[K] extends string ? K : never;
}[keyof T];

type UserStringKeys = StringKeys<User>; // "name" | "email"

Conditional Types

// Basic conditional
type IsArray<T> = T extends any[] ? true : false;
type A = IsArray<string[]>;  // true
type B = IsArray<number>;    // false

// Infer keyword — extract types
type ElementType<T> = T extends (infer U)[] ? U : T;
type C = ElementType<string[]>; // string
type D = ElementType<number>;   // number

// Extract return type of a promise
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type E = UnwrapPromise<Promise<string>>; // string

// Conditional with union distribution
type ToArray<T> = T extends any ? T[] : never;
type F = ToArray<string | number>; // string[] | number[]

// Prevent distribution with tuple wrapper
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;
type G = ToArrayNonDist<string | number>; // (string | number)[]

Frequently Asked Questions

What is the difference between an interface and a type alias in TypeScript?

Both interfaces and type aliases can describe object shapes and be extended, but they differ in key ways. Interfaces support declaration merging (multiple declarations with the same name are automatically combined), while type aliases do not. Type aliases can represent unions, intersections, tuples, primitives, and mapped types, which interfaces cannot. Use interfaces for public API contracts and class shapes; use type aliases for unions, intersections, and complex computed types.

When should I use generics in TypeScript?

Use generics when you need to create reusable components, functions, or classes that work with multiple types while preserving type safety. Common use cases include utility functions like identity or wrapper functions, data structures like linked lists or stacks, API response wrappers, and higher-order functions. Generics allow you to defer the concrete type to the call site, avoiding the need for any while maintaining full type inference.

What are utility types in TypeScript and which ones are most useful?

Utility types are built-in generic types that transform existing types into new ones. The most commonly used are: Partial<T> makes all properties optional, Required<T> makes all required, Pick<T, K> selects specific properties, Omit<T, K> removes properties, Record<K, V> creates typed dictionaries, Readonly<T> prevents mutation, ReturnType<T> extracts a function's return type, and Parameters<T> extracts parameter types as a tuple.