TypeScript Types Guide

A complete, example-driven reference for every TypeScript type concept — from primitives to advanced mapped and conditional types.

Primitive Types

TypeScript has 7 primitive types, matching JavaScript primitives plus two additional types for safety:

// JavaScript primitives
let name: string = "Alice";
let age: number = 30;           // includes integers and floats
let active: boolean = true;
let nothing: null = null;
let undef: undefined = undefined;
let id: bigint = 9007199254740993n;
let sym: symbol = Symbol("id");

// TypeScript extras
let val: any = "anything";       // disables type checking (avoid when possible)
let input: unknown = getData();  // safer than any — must narrow before use
let never: never;                // value that can never occur (exhaustive checks)
let empty: void = undefined;     // function return type for no return value

Interface vs Type Alias

Both define object shapes. Key differences:

Interface

interface User {
  id: number;
  name: string;
  email?: string;       // optional
  readonly role: string; // read-only
}

// Extending
interface Admin extends User {
  permissions: string[];
}

// Declaration merging (unique to interface)
interface User {
  createdAt: Date; // added to original User
}

Type Alias

type User = {
  id: number;
  name: string;
  email?: string;
};

// Intersection (equivalent to extends)
type Admin = User & { permissions: string[] };

// Can also represent non-objects:
type ID = string | number;
type Status = "active" | "inactive";
type Handler = (e: Event) => void;
type Maybe<T> = T | null | undefined;
Rule of thumb: Use interface for object shapes and public APIs (extensible). Use type for unions, intersections, primitives, and computed types.

Unions & Intersections

// Union — value can be one of several types
type ID = string | number;
type Status = "active" | "inactive" | "pending"; // literal union

// Discriminated union — each branch has a unique literal
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;
  }
}

// Intersection — value must satisfy all types
type WithTimestamps = { createdAt: Date; updatedAt: Date };
type UserRecord = User & WithTimestamps;

Generics

Generics allow writing reusable code that works over many types while maintaining type safety:

// Generic function
function first<T>(arr: T[]): T | undefined {
  return arr[0];
}
const num = first([1, 2, 3]);   // type: number | undefined
const str = first(["a", "b"]); // type: string | undefined

// Generic interface
interface ApiResponse<T> {
  data: T;
  status: number;
  error?: string;
}
type UserResponse = ApiResponse<User>;

// Generic constraints
function getField<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}
getField({ name: "Alice", age: 30 }, "name"); // ✓ string
getField({ name: "Alice" }, "missing");        // ✗ compile error

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

Utility Types

TypeScript ships with built-in utility types that transform existing types:

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

// Transform all fields
Partial<User>          // all fields optional: { id?: number; name?: string; ... }
Required<User>         // all fields required (removes ?)
Readonly<User>         // all fields read-only

// Pick or remove fields
Pick<User, "id" | "name">      // { id: number; name: string }
Omit<User, "password">         // all fields except password

// Records and mappings
Record<string, User>            // { [key: string]: User }
Record<"admin" | "user", User>  // { admin: User; user: User }

// Set operations on union types
type ABC = "a" | "b" | "c";
Exclude<ABC, "a">              // "b" | "c"
Extract<ABC, "a" | "d">        // "a"
NonNullable<string | null | undefined> // string

// Function-related
ReturnType<typeof fetchUser>   // infer return type
Parameters<typeof fetchUser>   // infer parameter tuple
Awaited<Promise<User>>         // User

Type Narrowing

Narrowing refines a union type to a specific member inside a conditional block:

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

// instanceof narrowing
function handleError(err: Error | string) {
  if (err instanceof Error) {
    console.log(err.stack);  // Error here
  } else {
    console.log(err);        // string here
  }
}

// 'in' narrowing
type Cat = { meow(): void };
type Dog = { bark(): void };
function speak(animal: Cat | Dog) {
  if ("meow" in animal) {
    animal.meow(); // Cat here
  } else {
    animal.bark(); // Dog here
  }
}

// Custom type predicate
function isUser(val: unknown): val is User {
  return (
    typeof val === "object" &&
    val !== null &&
    "id" in val &&
    "name" in val
  );
}

Advanced Types

// Mapped type — transform every key of a type
type Optional<T> = { [K in keyof T]?: T[K] };
type ReadOnly<T> = { readonly [K in keyof T]: T[K] };

// Conditional type
type IsArray<T> = T extends any[] ? true : false;
type IsArray_string = IsArray<string[]>; // true
type IsArray_number = IsArray<number>;   // false

// Infer in conditional types
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type Resolved = UnwrapPromise<Promise<User>>; // User

// Template literal types (TS 4.1+)
type EventName = "click" | "focus" | "blur";
type Handler = `on${Capitalize<EventName>}`;
// "onClick" | "onFocus" | "onBlur"

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

Convert your JavaScript code to TypeScript automatically?

Use the JS to TS Converter →