Special Sponsor:PromptBuilder— Fast, consistent prompt creation powered by 1,000+ expert templates.
Make your Product visible here.Contact Us

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

Frequently Asked Questions

What is the difference between interface and type in TypeScript?

Both define object shapes. Interfaces support declaration merging and are extensible with extends. Type aliases are more flexible — they can represent unions, intersections, primitives, and computed types. Use interface for object shapes and public APIs; use type for unions and everything else.

What is 'unknown' vs 'any' in TypeScript?

'any' disables all type checking — you can call methods, access properties, and assign it anywhere without errors. 'unknown' is the safe alternative: you must narrow the type before using it. Prefer 'unknown' for values that could be anything.

What is a generic type in TypeScript?

A generic type accepts type parameters written as <T>. Generics let you write reusable, type-safe functions and interfaces that work across multiple types. For example, Array<T> is a generic representing an array of any type T.

What are union types in TypeScript?

A union type represents a value that can be one of several types, written with |. For example, string | number means the value can be either. Use type narrowing (typeof, instanceof, or in) to determine the specific type at runtime.

What are mapped types in TypeScript?

Mapped types create new types by transforming the properties of an existing type using the syntax { [K in keyof T]: ... }. Built-in utility types like Partial<T>, Required<T>, and Readonly<T> are all implemented as mapped types.

From the blog

View all →

Convert your JavaScript code to TypeScript automatically?

Use the JS to TS Converter →