Interfaces and Type Aliases

Learn how to define custom types using interfaces and type aliases in TypeScript

Interfaces and Type Aliases

TypeScript provides powerful ways to define custom types that help you model your data and create more maintainable code.

Interfaces

Interfaces define the shape of an object, specifying what properties it should have and their types:

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

const user: User = {
  id: 1,
  name: "John Doe",
  email: "john@example.com",
  isActive: true
};

Optional Properties

Use the ? operator to make properties optional:

interface UserProfile {
  id: number;
  name: string;
  email?: string; // Optional
  avatar?: string; // Optional
}

const profile: UserProfile = {
  id: 1,
  name: "Jane Doe"
  // email and avatar are optional
};

Readonly Properties

Mark properties as readonly to prevent modification after creation:

interface Point {
  readonly x: number;
  readonly y: number;
}

const point: Point = { x: 10, y: 20 };
// point.x = 5; // Error: Cannot assign to 'x' because it is a read-only property

Function Types in Interfaces

Interfaces can describe function types:

interface SearchFunction {
  (source: string, subString: string): boolean;
}

const mySearch: SearchFunction = function(source: string, subString: string): boolean {
  return source.search(subString) > -1;
};

Extending Interfaces

Interfaces can extend other interfaces:

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

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

const myDog: Dog = {
  name: "Buddy",
  age: 3,
  breed: "Golden Retriever",
  bark() {
    console.log("Woof!");
  }
};

Type Aliases

Type aliases create a new name for a type:

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

// Union types
type Status = "pending" | "approved" | "rejected";

// Function types
type EventHandler = (event: Event) => void;

// Generic type aliases
type ApiResponse<T> = {
  data: T;
  status: number;
  message: string;
};

Interface vs Type Alias

Both can be used to define object shapes, but there are differences:

// Interface - can be extended and merged
interface UserInterface {
  name: string;
}

interface UserInterface {
  age: number; // Declaration merging
}

// Type alias - more flexible with unions and primitives
type UserType = {
  name: string;
} & {
  age: number; // Intersection
};

type StringOrNumber = string | number; // Union types

Index Signatures

For objects with dynamic property names:

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

const colors: StringDictionary = {
  red: "#FF0000",
  green: "#00FF00",
  blue: "#0000FF"
};

Nested Interfaces

Interfaces can contain other interfaces:

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

interface Company {
  name: string;
  address: Address;
  employees: User[];
}

Best Practices

  1. Use interfaces for object shapes: Especially when you might need to extend them
  2. Use type aliases for unions and primitives: More flexible for complex types
  3. Prefer composition over deep nesting: Keep interfaces focused and composable
  4. Use descriptive names: Make your types self-documenting

Exercise

Create an interface for a blog post that includes:

  • Required: id, title, content, author (User interface), publishedAt
  • Optional: tags (array of strings), featuredImage

Then create a type alias for different post statuses and add it to your interface.

Next lesson: We'll explore classes and how they work with TypeScript's type system!