Step 1 of 7 · Reading · ~3 min
Learn
Type System Essentials
Type Aliases vs Interfaces
Both type and interface give a name to a shape, and for ordinary objects they are interchangeable. By the end of this lesson you will know the few places where only one of them works, and you will use both together to model a recursive structure.
Interface
interface Product {
name: string;
price: number;
describe(): string;
}
interface Perishable extends Product { // inheritance with extends
days: number;
}
const milk: Perishable = {
name: 'Milk', price: 1.2, days: 7,
describe() { return `${this.name} lasts ${this.days} days`; }, // `this` is the object the method is called on
};
A class can promise to follow an interface: class Crate implements Product { ... } must then supply every member.
Type alias
type Product = {
name: string;
price: number;
};
Looks the same, but a type alias can name things an interface cannot:
type Id = string | number; // union
type Point = [number, number]; // tuple
type Callback = (code: number) => void; // function type
type Meters = number; // plain alias
type Cart = Product[]; // array of something
type Tagged = Product & { tag: string }; // intersection
Composition with & has a similar effect to extends:
type Perishable = Product & { days: number };
The differences that matter
Interfaces are open: declaration merging. Two interfaces with the same name in the same scope become one:
interface Settings { theme: string; }
interface Settings { fontSize: number; } // merged: Settings has both fields
That is how libraries let you add fields to types you do not own, such as Window or Express's Request. A repeated type Settings = ... is an error: duplicate identifier.
Types are closed but more flexible. Only type can be a union, tuple, primitive alias, or the result of a conditional or mapped type. interface Id = string | number does not parse.
Extending a conflict. interface B extends A is an error if B redeclares a field of A with an incompatible type. An intersection A & B does not complain; the conflicting field silently becomes never.
Recursion works with both. A thread of replies refers to itself:
type Reply = { text: string; replies: Reply[] };
The two can reference each other freely, which is how a union of interfaces is built:
interface Success { ok: true; value: number }
interface Failure { ok: false; error: string }
type Outcome = Success | Failure; // the union itself must be a type alias
Which to use
Use interface for object shapes that other code may extend, class contracts (implements), and declarations you want to be mergeable. Use type for unions, intersections, tuples, function types, mapped and conditional types, and plain aliases. For everyday objects either works: pick one convention and keep it consistent. For the type checker's performance on very large codebases, interfaces that extend each other are slightly cheaper than long intersections.
Common mistakes
- Trying to merge two
typealiases. Duplicate identifier; use an interface. - Writing
interface X = A | B. Unions needtype. - Prefixing interfaces with
I.IUseris a C# habit and is discouraged in modern TypeScript. - Assuming
extendsand&behave the same on conflicts. One errors, the other gives younever. - Switching styles file by file. Mixed conventions make code harder to read.
Your exercise
Model a small file system. A file has a size; a directory holds a list of entries, each of which is a file or a directory. That is a union of two object shapes that refer back to the union, so it uses both an interface and a type alias. The starter parses a JSON tree; you declare the types and write the function that walks it. Because a directory contains entries that may themselves be directories, that function calls itself on each child. Remember that a directory with no children still counts as one level of depth.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…