Appearance
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
implementskeyword.
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 stringInheritance
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;
}
};External Resources