#type vs interface
Both interface and type give a name to a type so you can reuse it. They overlap a lot — for a plain object shape they are interchangeable — which is exactly why interviewers like asking about the differences.
An interface declares a named object type: properties, methods, call signatures, index signatures. That's all it can describe. A type alias is a new name for any type expression — an object shape, but also a primitive, a union, a tuple, a function type, or the result of a mapped or conditional type. The alias doesn't create anything new; it's a label stuck on whatever is to the right of the =.
A useful mental model: an interface is a named contract that stays open (you can add to it later, see Declaration merging); a type alias is a closed label for a type expression. And because TypeScript is structural, neither one creates a "brand" — two types with the same members are compatible no matter what they're called or how they were declared.
interface PersonI {
name: string;
age?: number;
}
type PersonT = {
name: string;
age?: number;
};
type ID = string | number;
const a: PersonI = { name: "Ana" };
const b: PersonT = a; // fine: same members, different declaration kind
const id: ID = 42;interface PersonIandtype PersonTdescribe the same shape: a requiredname: stringand an optionalage?: number. The interface usesinterface Name { … }(no=); the alias usestype Name = { … };— the{ … }is an ordinary object type literal that the alias names.const a: PersonI = { name: "Ana" }— the checker compares the literal's type{ name: string }withPersonI:nameis present and a string,ageis optional so leaving it out is fine.const b: PersonT = a— the checker doesn't care thatawas declared with an interface. It checks structure: doesPersonIhave everythingPersonTrequires, with compatible types? Yes, so the assignment passes. This is structural typing: names don't matter, members do.type ID = string | numberis something an interface can't express — it isn't an object shape, it's a union of two primitives.const id: ID = 42passes because42is assignable tonumber, one member of the union.- At runtime all four type declarations are erased. The emitted JavaScript is just
const a = { name: "Ana" }; const b = a; const id = 42;.
Here is what only a type alias can name — every line is a type expression that isn't an object shape, or is computed from another type:
type PersonT = { name: string; age?: number };
type ID = string | number; // union
type Point = [x: number, y: number]; // tuple
type Callback = (err: Error | null) => void; // function type
type Status = "idle" | "loading" | "done"; // union of literal types
type Nullable<T> = T | null; // generic alias of anything
type PersonKeys = keyof PersonT; // computed from another type: "name" | "age"
type ReadonlyPerson = { readonly [K in keyof PersonT]: PersonT[K] }; // mapped typeIDandStatusare unions — "one of these". An interface always describes one object shape, so it has no way to say "a string or a number".Pointis a tuple type with labelled positions (see Tuples). An interface could describe something array-like, but not a fixed-length tuple this directly.Callbackis a function type expression. An interface can describe a function with a call signature (see Describing functions with types and interfaces), but the alias form is shorter.Nullable<T>andPersonKeysshow that an alias can be built from any type operator:T | nullfor whateverTis passed in, andkeyof PersonT, which the checker evaluates to"name" | "age".ReadonlyPersonis a mapped type (see Mapped types). Mapped and conditional types can only appear on the right of atypealias — an interface body can't contain them.
interface vs type comparison tableThe differences that matter in practice:
interface | type alias | |
|---|---|---|
| Object shapes | Yes | Yes |
| Unions, tuples, primitives, mapped and conditional types | No | Yes |
| How you build on another type | extends | intersection & |
| Conflicting property when building on another type | Error at the declaration | Property silently becomes never |
| Same name declared twice | Merges into one interface | Duplicate identifier error |
A class can implements it | Yes | Yes, if it's an object type (not a union) |
Assignable to Record<string, unknown> | No (no implicit index signature) | Yes |
The last row surprises people; it's explained under Index signatures. The extends-vs-& rows have their own topic: extends vs intersection.