Lesson 6 of 25
Type Aliases vs Interfaces
What a type alias can do that an interface can't (and the reverse), and a practical rule for choosing between them.
interface and type overlap enough that beginners reasonably wonder why
TypeScript has both. They're not interchangeable, though — each can express
things the other genuinely cannot.
The overlap
For a plain object shape, they read almost identically:
interface UserA {
name: string;
age: number;
}
type UserB = {
name: string;
age: number;
};Both are used the same way, both are checked structurally, both support
optional (?) and readonly properties. If this were the whole story,
there'd be no reason for two keywords.
What only type can do
A type alias can name any type, not just an object shape — a union, an
intersection, a tuple, a function type, or even a primitive:
type Status = "pending" | "done" | "failed"; // a union of literals
type ID = string | number; // a union of primitives
type Point = [number, number]; // a tuple
type Comparator = (a: number, b: number) => number; // a function typeNone of these have an equivalent interface form — interface can only
describe an object's shape (with a narrow exception for function and
constructor call signatures). A union of string literals, in particular,
comes up constantly and is type-only.
What only interface can do
Two things, both covered in the previous lesson:
Declaration merging — the same interface name declared twice combines into one:
interface Config {
timeout: number;
}
interface Config {
retries: number;
}
// Config now has both timeout and retries.type Config = { timeout: number } followed by type Config = { retries: number } in the same scope is a compile error — a type alias name can only
be bound once.
extends, for object shapes, produces slightly better error messages in
some editors than the type-alias equivalent, & (intersection):
interface Base {
id: string;
}
interface Extended extends Base {
name: string;
}
type BaseT = { id: string };
type ExtendedT = BaseT & { name: string };Both Extended and ExtendedT end up requiring the same two properties in
practice. The difference is mostly about tooling and how conflicts are
reported, not about what's ultimately allowed.
Intersection vs extends, when they actually disagree
There's one case worth knowing, because it explains why interfaces don't
just get replaced by type entirely: intersecting two object types with a
conflicting property produces never for that property (an impossible
type), while extends on an interface simply rejects the conflict outright
at the declaration:
type A = { value: string };
type B = { value: number };
type AB = A & B; // { value: never } — impossible to satisfy, but "compiles"
interface IA {
value: string;
}
interface IB extends IA {
value: number;
// Interface 'IB' incorrectly extends interface 'IA'.
// Types of property 'value' are incompatible.
}The interface version fails loudly, right where the mistake was made. The
type-alias version quietly produces a type nobody can ever actually satisfy,
and the error only shows up later, wherever someone tries to construct an
AB.
A practical rule
Most TypeScript style guides converge on something like this:
- Reach for
interfacewhen describing the shape of an object, especially one a consumer of your code might need to extend. - Reach for
typefor everything else — unions, tuples, function types, or any type built by combining others with&or|.
This isn't a rule the compiler enforces — both work for a plain object shape, and plenty of production codebases pick one and use it everywhere out of consistency rather than any technical requirement. What matters is knowing which situations genuinely require one over the other, which is the "declaration merging" and "union/tuple/function type" cases above.
Try it yourself
What to remember
- Only
typecan name a union, tuple, function type, or primitive alias;interfaceis restricted to object (and callable) shapes. - Only
interfacesupports declaration merging — redeclaring atypename is a compile error. &(intersection) is thetypeequivalent ofextends, but conflicting properties behave differently:&produces an unsatisfiablenever, while interfaceextendsrejects the conflict at the declaration.- Common convention:
interfacefor extensible object shapes,typefor everything else — not a compiler rule, but a useful default.
Check yourself
4 questions · pass 3/4 to unlock Union and Intersection Types
1.Which of these can only be written as a type alias, not an interface?
2.Given
type Point = { x: number; y: number }, can you later add azproperty by declaringtype Point = { z: number }again?3.Which syntax combines two object shapes into one using intersection rather than extends?
4.For a public library's exported object shapes, which is generally recommended and why?
4 left to answer