Interfaces

What Are Interfaces in TypeScript?

As explained above, type aliases are reusable names (aka aliases) for existing primitive or object types or their unions.

On the other hand, interfaces are standalone blueprints or contracts describing a type structure (aka type shape) of an object value.

Interfaces v. Type Aliases

Both interfaces and type aliases pertain to a type of a given value. However, they differ in some respects. The following separates interfaces from type aliases:

  • definition & definition syntax,

  • type limitations,

  • declaration merging,

  • union limitations,

  • mapping limitations,

  • extension syntax,

  • error messages, and

  • performance.

Definition & Defintion Syntax

A type alias is a name for another type or union of types. A type alias does not define a new type by itself but only gives name to an existing one.

type Point2D = { x: number, y: number }
type Point = { x: number, y: number, z: number } | Point2D

On the other hand, an interface is not just a name for a type. An interface creates a new standalone blueprint or contract for a structural type. Not using an assignment operator at interface definition is symptomatic of that.

interface Point { x: number, y: number }

Type Limitations

Type aliases can be used for both primitive and object types.

type Word = string
type Person = { firstName: string, lastName: string }

On the other hand, interfaces cannot be used for primitive types. Even their syntax disallows that.

interface Person { firstName: string, lastName: string }

Declaration Merging

Probably the most important distinction between interfaces and type aliases is the ability of interfaces of declaration merging.

Using twice or more the same name for type aliases results in TypeScript problem Duplicate identifier.

type Point = { x: number, y: number }
type Point = { x: number, y: number, z: number } // TS: Duplicate identifier 'Point'..

However, it is acceptable to declare an interface with the same name more than once.

A redeclaration of an interface with the same name as the one already declared does not override it but merges with it creating a combination of properties of all declarations.

interface Point { x: number, y: number }
interface Point { z: number }

function logPoint(point: Point) {
  console.log(point)
}

logPoint({ x: 42, y: 42 })
/* TS: Argument of type '{ x: number; y: number; }' is not assignable to parameter of type 'Point'.
Property 'z' is missing in type '{ x: number; y: number; }' but required in type 'Point'. */

logPoint({ x: 42, y: 42, z: 42 }) // OK

The most prominent use case for declaration merging is a redeclaring type for an object imported from an external library which properties are being altered.

Union Limitations

Although, interfaces have the ability of redeclaration they are deemed fixed contracts or blueprints and therefore cannot define a union type like type aliases.

type Point2D = { x: string, y: string }
type Point3D = { x:string, y: string, z: string }
type Point = Point2D | Point3D // Not possible with interfaces.

Mapping Limitations

Using type aliases it is possible to map property keys from another type.

enum Coordinates { x, y, z }

type Point = {
  [key in Coordinates]: number
}

/* It defines the following type aliased with "Point":
type Point = {
  0: number;
  1: number;
  2: number;
} */

The above is not possible with interfaces.

enum Coordinates { x, y, z }

interface Point {
  [k in Coordinates]: number // TS: A mapped type may not declare properties or methods.
}

Extension Syntax & Limitations

Interfaces can be combined with other interfaces. Types can be combined with other types. Even interfaces and types can be combined.

An interface can be combined with other interface or type alias using the keyword extends. An extended interface has its properties combined with the other interface or type alias properties.

interface Point2D {
  x: number
  y: number
}

interface Point3D extends Point2D {
  z: number
}

const log3DPoint = (point: Point3D) => {
  console.log(point)
}

log3DPoint({ x: 42, y: 42 })
// TS: Argument of type '{ x: number; y: number; }' is not assignable to parameter of type 'Point3D'.
// TS: Property 'z' is missing in type '{ x: number; y: number; }' but required in type 'Point3D'.

An alias or literal type can be combined with an interface, an aliased type or a literal type using the & operator. When a type is being combined with an interface, aliased type or literal type it said that it is being intersected with it and the properties of those entities are being combined.

type Point2D = {
  x: number
  y: number
}

type Point3D = Point2D & {
  z: number
}

const log3DPoint = (point: Point3D) => {
  console.log(point)
}

log3DPoint({ x: 42, y: 42 })
// TS: Argument of type '{ x: number; y: number; }' is not assignable to parameter of type 'Point3D'.
// TS: Property 'z' is missing in type '{ x: number; y: number; }' but required in type '{ z: number; }'.

Error Messages & Editor Tooltips

As already noted defining an interface creates a new type blueprint or contract for an object whereas type alias only gives a reusable name to an already existing type.

Due to the above, interfaces and type aliases are presented differently in error messages and editor tooltips.

Performance

As presented under Microsoft Wiki TypeScript Performance extending interfaces as opposed to intersecting types is preferred due to among others performance issues.