Skip to content
beginner Phase 1 · TypeScript Fundamentals

Interfaces & Type Aliases

Define object shapes with interfaces and create reusable type aliases.

1h
0 problems
Topic Progress 0%

Defining Interfaces

Defining Interfaces

Interfaces describe the shape of objects. They define what properties and methods an object must have.

Basic Interface

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

const user: User = {
  id: 1,
  name: 'Alice',
  email: 'alice@example.com'
};

// Error: missing property 'email'
const badUser: User = {
  id: 2,
  name: 'Bob'
};

Optional Properties

interface UserProfile {
  id: number;
  name: string;
  bio?: string;        // optional
  avatar?: string;     // optional
}

const profile: UserProfile = {
  id: 1,
  name: 'Alice'
  // bio and avatar are optional
};

Readonly Properties

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

const config: Config = {
  apiUrl: 'https://api.example.com',
  timeout: 5000
};

// config.apiUrl = 'other'; // Error: cannot assign to readonly

Function Types in Interfaces

interface MathOperations {
  add(a: number, b: number): number;
  subtract: (a: number, b: number) => number;
}

const math: MathOperations = {
  add: (a, b) => a + b,
  subtract: (a, b) => a - b
};

Extending Interfaces

Extending Interfaces

Interfaces can extend other interfaces to build complex type hierarchies.

Single Inheritance

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

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

const myDog: Dog = {
  name: 'Rex',
  age: 5,
  breed: 'Labrador',
  bark() { console.log('Woof!'); }
};

Multiple Inheritance

interface Printable {
  print(): void;
}

interface Loggable {
  log(): void;
}

interface Document extends Printable, Loggable {
  title: string;
  content: string;
}

const doc: Document = {
  title: 'My Doc',
  content: 'Hello World',
  print() { console.log(this.title); },
  log() { console.log(this.content); }
};

Extending Multiple Interfaces

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

interface SoftDeletable {
  deletedAt: Date | null;
}

interface BaseEntity {
  id: number;
}

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

Interface Merging (Declaration Merging)

interface Window {
  myCustomProp: string;
}

// This merges with the existing Window interface
// allowing myCustomProp to be used on window object

Structural Typing

Structural Typing

TypeScript uses structural typing: objects are compatible if they have the same shape, regardless of explicit declaration.

Structural Compatibility

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

function logPoint(p: Point) {
  console.log(`${p.x}, ${p.y}`);
}

// This works - the object has the right shape
class MyPoint {
  x: number;
  y: number;
  constructor(x: number, y: number) {
    this.x = x;
    this.y = y;
  }
}

const mp = new MyPoint(10, 20);
logPoint(mp); // OK - MyPoint is structurally compatible with Point

Excess Property Checks

interface Square {
  width: number;
  height: number;
}

// Error: object literal may only specify known properties
const sq: Square = {
  width: 10,
  height: 10,
  color: 'red'  // Error!
};

// Workaround: assign to intermediate variable
const tmp = { width: 10, height: 10, color: 'red' };
const sq2: Square = tmp; // OK

Index Signatures

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

const dict: StringMap = {
  hello: 'world',
  foo: 'bar'
};

interface NumberDict {
  [key: string]: number;
  length: number; // must match index signature type
}

Record Utility Type

// Record is equivalent to an interface with index signature
const scores: Record<string, number> = {
  alice: 95,
  bob: 87
};

Type vs Interface

Type vs Interface

Both type aliases and interfaces define object shapes, but have key differences.

When to Use Interface

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

// This extends the same interface
interface User {
  age: number;
}

// Result: User has both name and age
const user: User = { name: 'Alice', age: 30 };

// Interfaces are better for: object shapes, class contracts, extendable APIs
interface Repository<T> {
  findById(id: string): Promise<T>;
  findAll(): Promise<T[]>;
  save(entity: T): Promise<T>;
  delete(id: string): Promise<void>;
}

When to Use Type Alias

// Types support unions and intersections
type Result<T> = Success<T> | Failure;

// Types can compute new types
type UserKeys = keyof User;
type UserName = User['name'];

// Types can use mapped types
type Readonly<T> = { readonly [K in keyof T]: T[K] };

// Types can be primitives
type ID = string;
type Callback = () => void;

Comparison Table

Feature Interface Type Alias
Declaration Merging Yes No
Union Types No Yes
Intersection Yes (extends) Yes (&)
Implements (class) Yes Yes
Map/Filter types No Yes
Extends other types Yes Yes

Best Practice

  • Use interfaces for object shapes and class contracts
  • Use type aliases for unions, intersections, and computed types
  • Be consistent within your codebase