← thecodex.expert · The Codex Family of Knowledge
TypeScript

Interfaces vs Types

Declare the same interface name twice, and TypeScript merges them into one — try that with type, and it’s a compile error every time.

TypeScript 6.0 interface can merge; type can’t Last verified:
Where this bites you

"Interchangeable" has one sharp exception. A type alias for an object shape is assignable to Record<string, string>, because TypeScript treats it as having an implicit index signature, while an interface with exactly the same properties is not, because interfaces can be extended later by declaration merging, so TypeScript won't assume it knows every key. The same object literal passes or fails depending only on which keyword declared its type.

TypeScriptindex_signature_bug.ts
interface UserI { name: string; city: string }
type UserT = { name: string; city: string };

function toQuery(params: Record<string, string>): string {
    return new URLSearchParams(params).toString();
}

const a: UserT = { name: "Asha", city: "Pune" };
const b: UserI = { name: "Ravi", city: "Delhi" };

console.log(toQuery(a));   // fine
console.log(toQuery(b));
// error TS2345: Argument of type 'UserI' is not assignable to parameter of type 'Record<string, string>'.
//   Index signature for type 'string' is missing in type 'UserI'.
Canonical Definition

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.

TypeScriptequivalent.ts
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 fine

So "nearly identically" above is precise: the difference is index-signature compatibility, and it follows directly from declaration merging below. A type alias is closed, so TypeScript can see all of its keys; an interface is open, so it can't. When an interface-typed value has to go into something keyed by arbitrary strings, either declare the shape as a type, or pass a copy, which TypeScript types as a fresh object literal:

TypeScriptindex_signature_fix.ts
interface UserI { name: string; city: string }

function toQuery(params: Record<string, string>): string {
    return new URLSearchParams(params).toString();
}

const b: UserI = { name: "Ravi", city: "Delhi" };

console.log(toQuery({ ...b }));   // name=Ravi&city=Delhi

Declaration 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.

TypeScriptdeclaration_merging.ts
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.

TypeScripttype_only.ts
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

Sources

1
Microsoft. TypeScript Handbook, "Differences Between Type Aliases and Interfaces", typescriptlang.org/docs/handbook/2/everyday-types.html.