TypeScript Cheatsheet

Interfaces and Type Aliases

Use this TypeScript reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.

Interface Declaration

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

// Optional property
interface Config {
  host: string;
  port?: number;   // number | undefined
}

// Readonly property
interface Point {
  readonly x: number;
  readonly y: number;
}

// Method shorthand vs property function syntax
interface Greeter {
  greet(name: string): string;           // method shorthand
  greetFn: (name: string) => string;    // property function
}

Type Alias Declaration

type UserId = string;
type Point = { x: number; y: number };
type Callback = (err: Error | null, data: string) => void;

// Union and intersection only possible with type, not interface
type StringOrNumber = string | number;
type Named = { name: string };
type Aged = { age: number };
type Person = Named & Aged;

// Recursive type alias
type Json =
  | string | number | boolean | null
  | Json[]
  | { [key: string]: Json };

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

Interface vs Type Alias: Key Differences

Featureinterfacetype
Declaration mergingYesNo
Extends another interfaceYes (extends)Via intersection (&)
Implements in classYesYes (if object shape)
Union typesNoYes
Intersection typesNo (use extends)Yes (&)
Conditional typesNoYes
Mapped typesNoYes
Computed property keysLimitedYes (via mapped)
Tuple typesNoYes
Primitive aliasesNoYes (type Id = string)

Prefer interface for public API shapes (object types, classes). Prefer type for unions, tuples, mapped types, and computed types.

Extending Interfaces

interface Animal {
  name: string;
}

interface Dog extends Animal {
  breed: string;
}

// Multiple inheritance
interface FlyingDog extends Dog, Animal {
  wingspan: number;
}

// Extending with modifications (can't narrow, but can widen)
interface Pet extends Animal {
  owner: string;
}

const d: Dog = { name: "Rex", breed: "Lab" };

Declaration Merging (interface only)

// Multiple declarations of the same interface are merged
interface Box {
  width: number;
}
interface Box {
  height: number;
}
// Box = { width: number; height: number }

// Useful for augmenting third-party types
interface Window {
  myCustomProp: string;
}

// Module augmentation
declare module "express" {
  interface Request {
    user?: { id: string };
  }
}

Index Signatures

// String index
interface StringMap {
  [key: string]: string;
}
const m: StringMap = { foo: "bar", baz: "qux" };

// Number index
interface NumberMap {
  [index: number]: string;
}
const arr: NumberMap = ["a", "b", "c"];

// Known properties + index signature (all props must match index type)
interface Mixed {
  length: number;
  [key: string]: number; // length must also be number — it is, so OK
}

// Readonly index signature
interface ReadonlyStringMap {
  readonly [key: string]: string;
}

Call Signatures and Construct Signatures

// Call signature — makes an object callable
interface Formatter {
  (value: string): string;
  prefix: string;          // also has a property
}

// Construct signature — makes an object newable
interface Constructor<T> {
  new (arg: string): T;
}

// Both
interface ClockConstructor {
  new (hour: number, minute: number): ClockInterface;
}
interface ClockInterface {
  tick(): void;
}

// Function with properties
interface FnWithProps {
  (x: number): number;
  defaultValue: number;
  reset(): void;
}

Intersection Types with type

type Named = { name: string };
type Aged  = { age: number };

// Intersection combines all properties
type Person = Named & Aged;
const p: Person = { name: "Alice", age: 30 };

// Merging with override (same key, different type = never)
type A = { x: string };
type B = { x: number };
type AB = A & B; // { x: never } — string & number = never

// Correct way to override: use Omit
type Override = Omit<A, "x"> & { x: number };

// Functional intersection
type LogAndRun = ((x: number) => string) & { debug: boolean };

Generics in Interfaces and Types

// Generic interface
interface Response<T> {
  data: T;
  status: number;
  message: string;
}
const res: Response<User[]> = { data: [], status: 200, message: "OK" };

// Generic type alias
type Nullable<T> = T | null;
type Pair<A, B> = { first: A; second: B };
type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E };

// Generic constraint
interface Lengthwise {
  length: number;
}
interface Collection<T extends Lengthwise> {
  items: T[];
  longest(): T;
}

// Default type parameter
interface Container<T = string> {
  value: T;
}
const c: Container = { value: "hello" }; // T defaults to string

Readonly and Partial Modifiers

// Readonly in interface
interface ImmutablePoint {
  readonly x: number;
  readonly y: number;
}

// Readonly<T> utility makes all props readonly
type FrozenUser = Readonly<User>;

// Partial<T> makes all props optional
type PartialUser = Partial<User>;

// Required<T> makes all props required
type FullConfig = Required<Config>;

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

Discriminated Unions with Type Aliases

// Each member has a literal discriminant field
type Shape =
  | { kind: "circle";    radius: number }
  | { kind: "square";    side: number }
  | { kind: "rectangle"; width: number; height: number };

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

// Result / Either pattern
type Result<T, E = string> =
  | { success: true;  value: T }
  | { success: false; error: E };

function parse(s: string): Result<number> {
  const n = Number(s);
  if (isNaN(n)) return { success: false, error: "Not a number" };
  return { success: true, value: n };
}

Recursive Interface / Type

// Recursive interface
interface TreeNode {
  value: number;
  left?: TreeNode;
  right?: TreeNode;
}

// Recursive type alias
type NestedList<T> = T | NestedList<T>[];

// Linked list
interface ListNode<T> {
  data: T;
  next: ListNode<T> | null;
}

// Deeply nested object
interface Category {
  name: string;
  children?: Category[];
}

Extracting and Transforming Types

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

// Pick specific properties
type PublicUser = Pick<User, "id" | "name" | "email">;

// Omit specific properties
type SafeUser = Omit<User, "password">;

// Extract from union
type OnlyStrings = Extract<string | number | boolean, string>; // string

// Exclude from union
type NoStrings = Exclude<string | number | boolean, string>; // number | boolean

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

// ReturnType
type FnReturn = ReturnType<() => User>; // User

// Parameters
type FnParams = Parameters<(a: string, b: number) => void>; // [string, number]

Interface Extending a Class

class Control {
  private state: any;
}

// Interface can extend a class (captures its shape including private/protected)
interface SelectableControl extends Control {
  select(): void;
}

// Only subclasses of Control can implement SelectableControl
class Button extends Control implements SelectableControl {
  select() {}
}

Hybrid Types

// An object that acts as both a function and has properties
interface Counter {
  (start: number): string;
  interval: number;
  reset(): void;
}

function getCounter(): Counter {
  const counter = function(start: number) { return String(start); } as Counter;
  counter.interval = 123;
  counter.reset = function() {};
  return counter;
}

const c = getCounter();
c(10);
c.interval = 5;
c.reset();