Type aliases, and interface versus type
The other way to name a shape is a type alias — type Member = { ... }. It overlaps heavily with
an interface, which raises the question every TypeScript newcomer asks: interface or type? This
lesson answers it, and shows the things a type alias can do that an interface cannot — which is where
the two genuinely differ.
Naming anything, not just objects
A type alias gives a name to any type — an object shape, but also a union, a primitive, a tuple, a function type, anything:
type Member = { name: string; age: number }; // an object shape (like an interface)
type ID = string | number; // a union — an interface CANNOT do this
type Status = "pending" | "paid" | "shipped"; // a literal union
type Point = [number, number]; // a tuple
type Handler = (event: string) => void; // a function type
type Nullable<T> = T | null; // a generic alias (generics module)
This is the first big difference: an interface can only describe an object (or function) shape; a
type alias can name anything. Those unions, tuples, and function types you have already been
writing — when you want to name them, you need a type, because an interface simply cannot express
string | number. So for most of TypeScript's more interesting types, the alias is the only option.
For object shapes, they are nearly identical
When aliasing an object shape, a type alias does almost exactly what an interface does:
type Member = {
name: string;
age: number;
};
const kavita: Member = { name: "Kavita", age: 33 };
Same excess-property checking, same structural compatibility, same usage. A type alias even composes:
instead of extends, you combine object types with an intersection (&):
type Person = { name: string; age: number };
type Employee = Person & { employeeId: string }; // Person AND the extra property
const ravi: Employee = { name: "Ravi", age: 40, employeeId: "E1" };
Person & { employeeId: string } means "has everything in Person and employeeId" — the alias
equivalent of interface Employee extends Person. So for an object shape, type and interface are
interchangeable in practice; the choice is mostly style.
The differences that actually matter
Two real distinctions decide it in the cases where it matters:
Interfaces merge; type aliases cannot. From the last lesson: declaring interface Window twice
combines them; declaring type Window twice is a "Duplicate identifier" error. So if you need
declaration merging — augmenting a global or a library type — you must use an interface.
Type aliases can express things interfaces cannot. Unions, tuples, primitives, mapped types,
conditional types (the advanced-types module) — all of these need a type. An interface is limited to
object and function shapes.
Everything else people cite (subtle performance differences, error-message formatting) is minor and rarely matters. The two real questions are "do I need merging?" (→ interface) and "am I naming something other than an object shape?" (→ type).
The recommendation
There are two defensible conventions, and you should follow whichever your team uses:
Convention A — "interfaces for object shapes, types for everything else." Use interface when you
are describing the shape of an object (a Member, a Repository), and type for unions, tuples,
function types, and anything more complex. This is a common style and reads well: an interface
signals "this is the shape of a thing", a type signals "this is a named type expression". The
merging ability of interfaces is a bonus if a library needs it.
Convention B — "types by default, interfaces only when you need merging." Use type for
everything, and reach for interface only when you specifically need declaration merging (augmenting a
library). This has the appeal of consistency — one keyword for almost everything — and some large
codebases prefer it.
Both are fine. The one thing that is not fine is being inconsistent — mixing them arbitrarily
within a codebase for the same kind of thing. Pick a convention (or adopt your team's) and follow it.
This course leans toward Convention A — interface for object shapes, type for unions and
compositions — because it reads clearly and matches a lot of library and framework code you will
encounter, but the important thing is consistency, not which one you pick.
The mental shortcut
When you reach for a name and are unsure which to use:
- Naming an object's shape, and want it possibly extensible/mergeable? →
interface. - Naming a union, tuple, function type, or anything not a plain object shape? →
type(you have no choice). - Naming a simple object shape and your team uses
typeeverywhere? →type.
You will not agonise over this for long — in practice you reach for type constantly (because so many
useful types are unions and compositions) and interface for your main object shapes, and it becomes
automatic.
Check your work
What a type alias names. Any type — object shapes, unions, tuples, primitives, function types, generics.
The first big difference from an interface. An interface can only describe object/function shapes;
a type alias can name anything, including a union like string | number.
How a type alias composes object shapes. With an intersection & — Person & { extra: string } —
the equivalent of interface extends.
The two differences that actually matter. Interfaces merge (type aliases cannot); type aliases can express unions/tuples/mapped/conditional types (interfaces cannot).
When you must use an interface. When you need declaration merging (augmenting a global or library).
When you must use a type alias. When naming anything that is not a plain object/function shape.
The two defensible conventions. Interfaces for object shapes + types for the rest; or types by default + interfaces only for merging.
The rule that matters most. Consistency — pick a convention and follow it.
Practice
- Alias a union (
type ID = string | number), a tuple, and a function type. Try to write each as aninterfaceand confirm you cannot. - Alias an object shape and use it exactly like an interface. Confirm excess-property checking still applies.
- Compose object shapes with
&(Person & { employeeId: string }) and construct one. Omit a required property and read the error. - Declare a
typetwice with the same name and read the duplicate-identifier error. Then do the same with aninterfaceand confirm it merges. - For five real shapes (a user, a status union, a coordinate, an event handler, an API response),
decide
interfaceortypefor each and justify it. - Adopt one convention and rewrite a small mixed file to follow it consistently.
Official documentation
- TypeScript — Type Aliases — Naming any type.
- TypeScript — Differences Between Type Aliases and Interfaces — The official comparison.
- TypeScript — Intersection Types — Composing with
&.
Next: optional and readonly properties.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship