Interfaces

An interface describes the shape of an object — what properties it has and what types they are — without saying anything about where that object comes from. Any object with a matching shape satisfies the interface, whether or not it was ever explicitly declared to.

Defining and using an interface

TS user.ts
interface User {
  name: string;
  age: number;
}

function describe(user: User) {
  return `${user.name} is ${user.age} years old`;
}

const alice: User = { name: "Alice", age: 30 };
console.log(describe(alice));
Output
Alice is 30 years old

describe doesn't care how alice was created — it only cares that whatever gets passed in has a name: string and an age: number. This is called structural typing: TypeScript checks shape, not declared identity.

Optional and readonly properties

TS optional.ts
interface Product {
  readonly id: number;
  name: string;
  discount?: number;
}

const item: Product = { id: 1, name: "Mug" };
console.log(item.discount);

item.id = 2;
tsc output
error TS2540: Cannot assign to 'id' because it is a read-only property.

The modifiers work exactly as they do in type aliases: ? makes a property optional and readonly prevents reassignment.

Extending an interface

extends builds a new interface on top of one or more existing ones:

TS extend.ts
interface Animal {
  name: string;
}

interface Pet {
  owner: string;
}

interface Dog extends Animal, Pet {
  breed: string;
}

const rex: Dog = { name: "Rex", owner: "Sam", breed: "Labrador" };
console.log(`${rex.name} is ${rex.owner}'s ${rex.breed}`);
Output
Rex is Sam's Labrador

Dog has all the properties of both Animal and Pet, plus its own. If an extending interface redeclares a property with an incompatible type, TypeScript reports an error at the interface itself.

Describing methods and functions

TS methods.ts
interface Logger {
  prefix: string;
  log(message: string): void;
}

interface Formatter {
  (value: number): string;
}

const logger: Logger = {
  prefix: "[app]",
  log(message) {
    console.log(`${this.prefix} ${message}`);
  },
};

const money: Formatter = value => `$${value.toFixed(2)}`;
logger.log(money(9.5));
Output
[app] $9.50

Formatter is a call signature: an interface for something you can call like a function. Notice that the message and value parameters didn't need annotations — they were inferred from the interface. This is called contextual typing.

Classes implementing interfaces

TS implements.ts
interface Shape {
  area(): number;
}

class Circle implements Shape {
  constructor(private radius: number) {}
  area() {
    return Math.PI * this.radius ** 2;
  }
}

const shapes: Shape[] = [new Circle(1), { area: () => 4 }];
shapes.forEach(s => console.log(s.area().toFixed(2)));
Output
3.14
4.00

implements asks the compiler to check the class against the interface. Note the second element — a plain object — also counts as a Shape, because structural typing only cares about the shape.

Interface or type alias?

For describing object shapes they're almost interchangeable. The practical differences:

interfacetype
Object shapesYesYes
Unions, tuples, primitives, function typesNoYes — type ID = string | number
ExtendingextendsIntersection & (lesson 12)
Can be reopened and added to laterYes — declaration merging (lesson 34)No — duplicate name is an error
Mapped and conditional typesNoYes (lessons 28–29)

A common convention: use interface for object shapes that other code may extend (especially public APIs and classes), and type for unions, tuples, function types and anything computed. The most important thing is to be consistent within a project.

Try it yourself

Try it yourself
Console output
Note: developers coming from Java or C# often expect a class to declare implements before it can be used as an interface type. In TypeScript that's unnecessary — any value with the right properties qualifies. implements is just an extra check that you've written the class correctly.