TechLead
Lesson 3 of 8
5 min read
TypeScript

Interfaces & Type Aliases

Define custom types with interfaces and type aliases, and learn when to use each

Type Aliases

Type aliases create a new name for a type:

// Simple type alias
type ID = string | number;
type Username = string;

// Object type alias
type User = {
  id: ID;
  name: string;
  email: string;
  age?: number;  // optional
};

// Function type alias
type Greeting = (name: string) => string;

// Union type alias
type Status = "pending" | "active" | "completed";

// Using type aliases
let userId: ID = "abc123";
let user: User = {
  id: 1,
  name: "Alice",
  email: "alice@example.com"
};

const greet: Greeting = (name) => `Hello, ${name}!`;

Interfaces

Interfaces define the shape of objects:

// Basic interface
interface User {
  id: number;
  name: string;
  email: string;
  age?: number;           // optional property
  readonly createdAt: Date;  // readonly property
}

// Using the interface
const user: User = {
  id: 1,
  name: "Alice",
  email: "alice@example.com",
  createdAt: new Date()
};

// Function interface
interface SearchFunc {
  (query: string, limit?: number): string[];
}

const search: SearchFunc = (query, limit = 10) => {
  return ["result1", "result2"];
};

// Index signature
interface StringDictionary {
  [key: string]: string;
}

const dict: StringDictionary = {
  hello: "world",
  foo: "bar"
};

Extending Interfaces

// Base interface
interface Animal {
  name: string;
  age: number;
}

// Extending a single interface
interface Dog extends Animal {
  breed: string;
  bark(): void;
}

const dog: Dog = {
  name: "Max",
  age: 3,
  breed: "Labrador",
  bark() {
    console.log("Woof!");
  }
};

// Extending multiple interfaces
interface Timestamps {
  createdAt: Date;
  updatedAt: Date;
}

interface Post extends Animal, Timestamps {
  title: string;
  content: string;
}

// Extending with override (must be compatible)
interface SpecialAnimal extends Animal {
  age: 1 | 2 | 3;  // narrower type is OK
}

Intersection Types

// Type aliases can use intersections
type Animal = {
  name: string;
  age: number;
};

type CanFly = {
  fly(): void;
  wingspan: number;
};

// Combine types with &
type Bird = Animal & CanFly;

const eagle: Bird = {
  name: "Eagle",
  age: 5,
  wingspan: 2.3,
  fly() {
    console.log("Flying!");
  }
};

// Intersection with interfaces too
interface Person {
  name: string;
}

interface Employee {
  employeeId: number;
  department: string;
}

type Staff = Person & Employee;

const staff: Staff = {
  name: "Alice",
  employeeId: 123,
  department: "Engineering"
};

Interface Declaration Merging

Interfaces can be declared multiple times and merge:

// First declaration
interface Config {
  apiUrl: string;
}

// Second declaration (merges with first)
interface Config {
  timeout: number;
}

// Result: Config has both properties
const config: Config = {
  apiUrl: "https://api.example.com",
  timeout: 5000
};

// Useful for extending library types
interface Window {
  myCustomProperty: string;
}

// Now window.myCustomProperty is typed

Type Aliases vs Interfaces

// Both can define object shapes
type UserType = {
  name: string;
  age: number;
};

interface UserInterface {
  name: string;
  age: number;
}

// Type aliases can define primitives, unions, tuples
type ID = string | number;           // Can't do with interface
type Coordinates = [number, number]; // Can't do with interface
type Status = "active" | "inactive"; // Can't do with interface

// Interfaces can be extended and merged
interface Animal {
  name: string;
}
interface Dog extends Animal {
  breed: string;
}

// Types use intersection for composition
type Animal2 = { name: string };
type Dog2 = Animal2 & { breed: string };

// Interfaces support declaration merging
interface User { name: string; }
interface User { age: number; }  // Merges

// Type aliases can't merge
// type User = { name: string };
// type User = { age: number };  // Error: Duplicate identifier

When to Use What

Use Interfaces When:

  • • Defining object shapes
  • • Creating contracts for classes
  • • You need declaration merging
  • • Working with libraries/APIs

Use Type Aliases When:

  • • Defining union types
  • • Defining tuple types
  • • Creating type utilities
  • • Aliasing primitives

Practical Examples

// API Response typing
interface ApiResponse<T> {
  data: T;
  status: number;
  message: string;
}

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

type UserResponse = ApiResponse<User>;
type UsersResponse = ApiResponse<User[]>;

// Component props
interface ButtonProps {
  label: string;
  onClick: () => void;
  variant?: "primary" | "secondary" | "danger";
  disabled?: boolean;
}

// Form data
interface LoginForm {
  username: string;
  password: string;
  rememberMe?: boolean;
}

// Event handlers
type ClickHandler = (event: MouseEvent) => void;
type ChangeHandler = (value: string) => void;

Key Takeaways

  • • Type aliases create new names for types
  • • Interfaces define contracts for object shapes
  • • Interfaces can extend other interfaces
  • • Type aliases use & for intersections
  • • Interfaces support declaration merging
  • • Use interfaces for objects, types for unions/primitives

Continue Learning