Both interface and type describe an object's shape, and for most everyday use they're interchangeable — a value satisfying one will satisfy the equivalent other. The genuine, load-bearing difference is declaration merging: multiple interface declarations with the same name automatically combine into one, which type aliases cannot do at all (redeclaring the same type name is a compile error). Conversely, only type can directly name a union, intersection, tuple, or primitive — interface is limited to object shapes.
Both describe an object shape, nearly identically
These two produce equivalent type-checking behavior — a value assignable to one is assignable to the other.
interface UserI {
name: string;
age: number;
}
type UserT = {
name: string;
age: number;
};
const u1: UserI = { name: "Alice", age: 30 }; // fine
const u2: UserT = { name: "Bob", age: 25 }; // also fineDeclaration merging: only interface can do this
This is genuinely useful for extending a type defined elsewhere (a library's global types, for example) — and it's the one thing a type alias fundamentally cannot replicate.
interface Window {
title: string;
}
interface Window { // a SECOND declaration of the same name
ts: number;
}
// they automatically merge into one interface with BOTH properties:
// interface Window { title: string; ts: number; }
// type Window = { title: string };
// type Window = { ts: number }; // COMPILE ERROR: Duplicate identifier 'Window'Only type can name a union, intersection, or primitive
interface is restricted to describing object shapes — it cannot directly name a union of string literals, a primitive alias, or a tuple the way type can.
type Status = "active" | "inactive"; // fine — type can name a union directly
// interface Status = "active" | "inactive"; // not valid syntax at all
type ID = string | number; // fine
type Coordinates = [number, number]; // fine — a tuple