Back to TypeScript tutorials
Intermediate13 min read

Interfaces & Type Aliases

Define object shapes, contracts, and reusable type definitions with interfaces and type aliases.

Interfaces for Object Shapes

Interfaces describe the shape of objects — property names, types, and optionality. They are ideal for public API contracts, React component props, and service boundaries that multiple modules consume.

Interfaces support declaration merging: two declarations with the same name merge into one. This is useful for augmenting third-party module types but can surprise teams if overused in application code.

  • Describe object property contracts
  • Declaration merging for module augmentation
  • Best for public API surfaces
interface User {
  id: string;
  name: string;
  email?: string;
}

Type Aliases for Flexibility

Type aliases name any type — unions, intersections, primitives, tuples, and mapped types. Use them when the type is not a plain object shape or when composing complex unions.

`type ID = string | number` and `type Result<T> = { ok: true; data: T } | { ok: false; error: string }` are idiomatic alias patterns. Aliases do not merge — each name is a single definition.

  • Alias any type, not just objects
  • Unions and discriminated results
  • No declaration merging
type Status = 'pending' | 'active' | 'archived';
type ApiResult<T> = { data: T } | { error: string };

Extending and Intersecting

Interfaces extend with `extends` keyword, creating subtypes that inherit properties. Type aliases combine with `&` intersection: `type Admin = User & { permissions: string[] }`.

For overlapping object types, intersections merge properties; conflicting property types become an intersection of those types (often `never` if incompatible). Prefer `extends` for OOP-style hierarchies and `&` for compositional mixing.

  • `extends` for interface inheritance
  • `&` for type intersection
  • Watch for incompatible property conflicts
interface Employee extends User {
  department: string;
}

Readonly and Optional Properties

`readonly` prevents reassignment after initialization — use for IDs and immutable config. Optional properties (`?`) allow absence; access them with optional chaining or explicit undefined checks.

`Readonly<T>` utility makes all properties readonly. Combine with `ReadonlyArray<T>` or `readonly T[]` for immutable collections in state management patterns.

  • `readonly` for immutable fields
  • Optional `?` for absent properties
  • `Readonly<T>` utility for deep immutability hints
interface Config {
  readonly apiUrl: string;
  debug?: boolean;
}

Choosing Interface vs Type

Convention varies by team: many codebases use `interface` for object shapes and `type` for everything else. Performance differences are negligible — choose for readability and tooling behavior.

Use interfaces when you expect augmentation or extension by other modules. Use type aliases for unions, mapped types, and conditional types. Consistency within a codebase matters more than the specific rule.

  • Interface for extensible object contracts
  • Type alias for unions and advanced types
  • Team consistency over dogma

Get In Touch


Ready to discuss your next project? Drop me a message.