type or interface
A workshop keeps its lock descriptions in two ways. Some go into a ring binder: it holds only drawings, but a new sheet can be added later, and the binder keeps all the sheets together. Others get a label with a name. A label can be stuck on anything — a drawing, a box marked "this lock or that one", a pair of items. But a second label with the same name cannot be stuck on.
interface and type work the same way: an interface is the binder, a type alias is the label. Both describe an object's shape, and most of the time there is no difference between them. They differ in two places, and interviewers like to ask about them: "How does type differ from interface?"
The same for objects
Let's describe one shape both ways:
type PointType = {
x: number;
y: number;
};
interface PointShape {
x: number;
y: number;
}
const a: PointType = { x: 1, y: 2 };
const b: PointShape = a;
console.log(b.x + b.y); // 3Let's go through the example:
PointTypeandPointShapedescribe the same shape: numbersxandy.ais declared as aPointType, and the lineconst b: PointShape = a;passes anyway. Types are compared by shape, and the shapes match.- The program prints
3.
For an object's shape, it does not matter which way it was written:
- tsc checks a literal the same way: a missing property is
TS2741, an extra one isTS2353; ?andreadonlywork in both;- both are erased and do not reach the JavaScript;
- a shape is extended with
extendson an interface and with&on an alias.
Only type: unions and tuples
A label can be stuck on anything, while the binder holds only drawings. An alias names any type:
type Id = string | number;
type Pair = [string, number];
const id: Id = 42;
const entry: Pair = ["apple", 5];
console.log(id, entry[0]); // 42 appleAn interface, on the other hand, describes only an object's shape. You cannot write the union string | number or a tuple with it: tsc cannot even parse interface Id = string | number; and answers error TS1005: '{' expected.
Only interface: declaration merging
Two interface declarations with one name do not clash; they add up to one:
interface Config {
port: number;
}
interface Config {
host: string;
}
const config: Config = { port: 8080, host: "localhost" };
console.log(`${config.host}:${config.port}`); // localhost:8080This is declaration merging: Config now has two required properties, port and host. tsc rejects an object without host — the property is missing, error TS2741. Merging is how other people's interfaces get extended. For example, in a file with no import or export, you add your own property to Window, the description of the browser window, by declaring another interface Window.
Which to choose
There is no strict rule, and a team picks one style. A common agreement is:
- an object's shape is an
interface: it can be extended withextendsand added to by merging; - unions, tuples and sets of literals are a
type, since an interface cannot describe them.
What matters most is that one project describes object shapes the same way everywhere. In this course, from this theme on, object shapes are interfaces.
Common mistake
The goal was to add a property to an alias with a second declaration, as with an interface:
type User = { name: string };
type User = { age: number }; // ✗error TS2300: Duplicate identifier 'User'.The message means "the name User is declared twice", and tsc puts it on both lines. Aliases do not merge: there is no second label with the same name. If you need a type with both properties, declare an interface twice or build a new type with &: type Person = User & { age: number };.
Key points
- For an object's shape,
typeandinterfaceare almost the same: the same checks, the same?andreadonly, and both are erased. - Unions and tuples can be named only with
type. - Two interfaces with one name merge into one, while two
types with one name are errorTS2300. - A team picks one style; a common rule is
interfacefor objects andtypefor the rest.