Skip to content

Interfaces ​

Interfaces in TypeScript are another way to define the shape of an object, specifying what properties and methods it should have. They serve as a contract that objects must follow and are often used to support object-oriented design patterns.

An interface is similar to a type alias for an object, but with added features like inheritance (and declaration merging, but we won't cover that here).

✅ In practice, you should use type aliases instead of interfaces in most situations. The main reason to use interfaces is to specify object structural inheritance with the implements keyword.

Usage ​

We can use an interface in the same way as a type alias shown on the previous page.

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

function printPoint(point: Point) {
  console.log(`Point at (${point.x}, ${point.y})`);
}

// These objects are compatible with Point even though they weren't created from the Point interface:
const obj1 = { x: 10, y: 20 };
const obj2 = { x: 5, y: 15, label: "origin" }; // Extra property is fine!

printPoint(obj1); // OK
printPoint(obj2); // OK - extra properties are ignored
printPoint({ x: 0, y: 99 }); // OK - object literal works too

// TypeScript catches structural mismatches:
// const obj3 = { x: 10 }; // Error: missing 'y' property
// const obj4 = { x: "10", y: 20 }; // Error: 'x' must be number, not string

Inheritance ​

Unlike type aliases, interfaces can extend other interfaces, creating a hierarchy:

ts
interface Shape {
  colour: string;
}

interface Square extends Shape {
  size: number;
}

// Square objects must have both colour and size:
let square: Square = { colour: "red", size: 10 };

Structural Inheritance ​

Interfaces are especially powerful when used with classes to specify what properties and methods the class must have. Use the implements keyword to ensure a class follows the interface contract.

ts
interface Drawable {
  zOrder: number;
  draw(gc: GraphicsContext): void; // function type definition
}

// Using interface with a class:
class Square implements Drawable {
  x: number;
  y: number;
  size: number;
  zOrder: number;

  constructor(x: number, y: number, size: number, zOrder: number) {
    this.x = x;
    this.y = y;
    this.size = size;
    this.zOrder = zOrder;
  }
  
  draw(gc: GraphicsContext): void {
    // Implementation would go here
    // gc.drawRect(this.x, this.y, this.size, this.size);
  }
}

Optional Properties ​

Interfaces can have optional properties using the ? operator:

ts
interface User {
  name: string;
  age: number;
  email?: string; // Optional property
}

// These are all valid:
const user1: User = { name: "Alice", age: 30 };
const user2: User = { name: "Bob", age: 25, email: "bob@example.com" };

Intersection Types ​

Intersection types work well with interfaces:

ts
interface Drawable {
  draw(): void;
}

interface Movable {
  move(dx: number, dy: number): void;
}

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

type GameObject = Drawable & Movable & Positionable;

// GameObject must implement all methods and have all properties:
const gameObject: GameObject = {
  x: 0,
  y: 0,
  draw() {
    console.log(`Drawing at (${this.x}, ${this.y})`);
  },
  move(dx: number, dy: number) {
    this.x += dx;
    this.y += dy;
  }
};