TypeScript Interfaces & Type Aliases

Interfaces and type aliases both describe the shape of objects — what properties they have and what types those properties are. They are nearly interchangeable in modern TypeScript, with a few differences.

Interfaces

An interface defines the contract that an object must follow:

TypeScript
interface User {
  name: string;
  age: number;
  email?: string;  // optional — may or may not exist
}

Now any variable typed as User must have name and age:

TypeScript
const ram: User = {
  name: "Ram",
  age: 22,
  // email is optional — no error if omitted
};

const sita: User = {
  name: "Sita",
  age: 21,
  email: "sita@example.com",
};

// ❌ Missing required property
const bad: User = { name: "Hari" };
// Error: Property 'age' is missing

Optional Properties

Add ? after the property name to make it optional:

TypeScript
interface Product {
  name: string;
  price: number;
  description?: string;  // optional
  rating?: number;       // optional
}

Readonly Properties

Use readonly to prevent modification after creation:

TypeScript
interface Config {
  readonly apiUrl: string;
  readonly timeout: number;
}

const config: Config = { apiUrl: "https://api.example.com", timeout: 5000 };
config.apiUrl = "https://other.com"; // Error: Cannot assign to 'apiUrl'

Extending Interfaces

Interfaces can inherit from other interfaces with extends:

TypeScript
interface Animal {
  name: string;
  sound: string;
}

interface Dog extends Animal {
  breed: string;
  fetch(): void;
}

const rex: Dog = {
  name: "Rex",
  sound: "Woof",
  breed: "Labrador",
  fetch() { console.log("Fetching!"); },
};

You can extend multiple interfaces:

TypeScript
interface HasName {
  name: string;
}

interface HasAge {
  age: number;
}

interface Person extends HasName, HasAge {
  email: string;
}

Type Aliases

Type aliases use the type keyword and can do everything interfaces can, plus more:

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

Type Aliases Can Do More

Feature Interface Type Alias
Object shapes ✅ ✅
Extends / inherits extends keyword & intersection
Declaration merging ✅ Yes ❌ No
Union types ❌ No ✅ string | number
Primitive aliases ❌ No ✅ type ID = string
Tuple types ❌ No ✅ [string, number]
Computed types ❌ No ✅ type Keys = keyof User
TypeScript
// Type alias for a union
type Status = "active" | "inactive" | "banned";

// Type alias for a tuple
type Pair = [string, number];

// Type alias for a function signature
type Callback = (error: Error | null, data?: string) => void;

Interface vs Type Alias — When to Use Which

Use interface when:

  • Defining the shape of an object
  • You might need declaration merging (e.g. augmenting third-party types)
  • Defining contracts for classes

Use type when:

  • Creating union types (string | number)
  • Creating intersection types (A & B)
  • Creating utility types (Partial<T>, Pick<T, K>)
  • Aliasing primitive or tuple types

Index Signatures

When you do not know the property names ahead of time:

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

const translations: Dictionary = {
  hello: "नमस्ते",
  goodbye: "अलविदा",
};

Nested Interfaces

TypeScript
interface Address {
  street: string;
  city: string;
  country: string;
}

interface User {
  name: string;
  age: number;
  address: Address;
}

const user: User = {
  name: "Sita",
  age: 21,
  address: {
    street: "Baneshwor",
    city: "Kathmandu",
    country: "Nepal",
  },
};

Tips

  • Start with interface for object shapes — it is the most common pattern and allows declaration merging.
  • Use type for unions, intersections, and computed types.
  • Name interfaces with PascalCase nouns (User, Product, ApiResponse).
  • Use optional properties (?) sparingly — prefer explicit null in function parameters.

Next: functions — where TypeScript's type system really shines.