codejump

AcademyTypeScript

Type aliases vs interfaces

What the two actually differ on — declaration merging, unions, and the error messages you get back — and the rule that settles it in a real codebase.

3 min readUpdated

3 exercises

Waiting at the end of this page.

The short answer

For an object shape, they are interchangeable. Both of these describe the same thing, and both are assignable to each other:

TypeScript
interface User {
  id: string;
  email: string;
}

type UserAlias = {
  id: string;
  email: string;
};

The differences only show up at the edges, and there are exactly three that matter. Everything else you have read about performance or "interfaces are for classes" is folklore.

Only an interface merges

Declare an interface twice and TypeScript merges the two declarations. Declare a type alias twice and it is an error.

TypeScript
interface Window {
  myAnalytics: (event: string) => void;
}
// Elsewhere, Window still has every built-in property — plus this one.

This is the reason interfaces exist in the type system and the reason library authors use them: merging is how you extend a type you do not own. It is also the reason not to use one for a shape internal to your app. A type nobody can reopen is a type nobody can quietly change from another file.

TypeScript
type User = { id: string };
type User = { name: string }; // Error: Duplicate identifier 'User'

That error is a feature.

Only a type can be something other than an object

A type alias names any type. An interface only ever describes an object shape.

TypeScript
type Status = 'idle' | 'loading' | 'failed';   // union
type Pair = [number, number];                   // tuple
type Handler = (event: Event) => void;          // function
type Keys = keyof User;                         // computed
type Nullable<T> = T | null;                    // generic over anything

None of those can be written as an interface. In practice this is what decides most of it: the moment a type is a union — and in TypeScript, a great many are — the question answers itself.

Extending is the same thing twice

interface B extends A and type B = A & { ... } produce the same assignability. The one real difference is what happens on a conflict.

TypeScript
interface A { id: string }
interface B extends A { id: number }
// Error: Interface 'B' incorrectly extends 'A'.

type C = { id: string } & { id: number };
// No error. C's id is 'string & number' — which is never.

The interface tells you at the declaration. The intersection tells you later, at the call site, with a message about never that reads like a puzzle. When you are combining shapes you expect to be compatible, the loud version is worth having.

Both work with implements, by the way — a class can implement a type alias just as well as an interface, as long as the alias describes an object.

Which to use

SituationUse
A union, tuple, function type, or anything computedtype
A public API others may need to augmentinterface
An object shape inside your own appeither — pick one and be consistent
Props for a React componenteither; type composes more predictably
Extending a type from a dependencyinterface, for the merging

The rule that survives code review: `type` by default, `interface` when you need merging. It gives one answer in the common case and a reason for the exception, which is all a convention has to do.

Now practice it

2 challenges · 1 quiz

Reading this page is the cheap half. These are the exercises that make you use it.