Skip to main content

Interfaces & Type Aliases

Defining Interfaces

An interface describes the shape of an object. It's one of the most common ways to define types in TypeScript:

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

const user: User = {
  name: "Sabaoon",
  email: "sabaoon@example.com",
  age: 25,
};

If you miss a required property or add an extra one, TypeScript will catch it:

// Error: Property 'email' is missing
const bad: User = { name: "Alice", age: 30 };

Optional & Readonly Properties

interface BlogPost {
  title: string;
  content: string;
  tags?: string[];           // optional
  readonly publishedAt: Date; // cannot be changed after creation
}

const post: BlogPost = {
  title: "Hello TypeScript",
  content: "...",
  publishedAt: new Date(),
};

// post.publishedAt = new Date(); // Error: read-only
post.tags = ["typescript"];        // OK — optional properties can be set later

Extending Interfaces

Interfaces can extend one or more other interfaces to build up complex types incrementally:

interface Timestamped {
  createdAt: Date;
  updatedAt: Date;
}

interface SoftDeletable {
  deletedAt: Date | null;
}

interface User extends Timestamped, SoftDeletable {
  id: string;
  name: string;
  email: string;
}

// User now requires all properties from all three interfaces
const user: User = {
  id: "1",
  name: "Sabaoon",
  email: "sabaoon@example.com",
  createdAt: new Date(),
  updatedAt: new Date(),
  deletedAt: null,
};

Type Aliases

Type aliases create a name for any type, not just objects:

// Alias for a union
type Status = "active" | "inactive" | "pending";

// Alias for a function signature
type Formatter = (input: string) => string;

// Alias for an object (looks similar to an interface)
type Point = {
  x: number;
  y: number;
};

// Alias for a tuple
type RGB = [red: number, green: number, blue: number];

Interface vs Type Alias

Both can describe object shapes, but they have key differences:

// Interface — can be extended and merged
interface Animal {
  name: string;
}

interface Animal {
  sound: string; // declaration merging: both declarations combine
}

const dog: Animal = { name: "Rex", sound: "Woof" }; // requires both

// Type — cannot be merged, but can represent unions, tuples, primitives
type Pet = Animal & { owner: string }; // intersection instead of extends

When to use which:

Featureinterfacetype
Object shapesYesYes
Extend / inheritextends keyword& intersection
Declaration mergingYesNo
UnionsNoYes
TuplesNoYes
PrimitivesNoYes

Rule of thumb: Use interface for object shapes (especially in libraries), use type for everything else (unions, tuples, computed types).

Index Signatures

When you don't know all property names in advance:

interface Dictionary {
  [key: string]: string;
}

const colors: Dictionary = {
  primary: "#E21B1B",
  secondary: "#1a1a1a",
  background: "#ffffff",
};

// You can combine known and dynamic properties
interface Config {
  env: "development" | "production";
  [key: string]: string;
}

Record Utility Type

Record is a cleaner alternative to index signatures for simple key-value maps:

// Record<KeyType, ValueType>
type UserRoles = Record<string, "admin" | "editor" | "viewer">;

const roles: UserRoles = {
  alice: "admin",
  bob: "editor",
  charlie: "viewer",
};

// With a union key for strict keys
type Theme = Record<"light" | "dark", { bg: string; text: string }>;

const theme: Theme = {
  light: { bg: "#fff", text: "#000" },
  dark: { bg: "#000", text: "#fff" },
};

Intersection Types

Combine multiple types into one using &:

type HasId = { id: string };
type HasTimestamps = { createdAt: Date; updatedAt: Date };

type Entity = HasId & HasTimestamps;

// Entity requires id, createdAt, and updatedAt
const entity: Entity = {
  id: "abc",
  createdAt: new Date(),
  updatedAt: new Date(),
};

This is the type equivalent of interface extends — both let you compose smaller types into larger ones.