RizTech Academy logo
RizTech Academy
Objects, Interfaces and Type AliasesLesson 3 of 525 min

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 type everywhere? → 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

  1. Alias a union (type ID = string | number), a tuple, and a function type. Try to write each as an interface and confirm you cannot.
  2. Alias an object shape and use it exactly like an interface. Confirm excess-property checking still applies.
  3. Compose object shapes with & (Person & { employeeId: string }) and construct one. Omit a required property and read the error.
  4. Declare a type twice with the same name and read the duplicate-identifier error. Then do the same with an interface and confirm it merges.
  5. For five real shapes (a user, a status union, a coordinate, an event handler, an API response), decide interface or type for each and justify it.
  6. Adopt one convention and rewrite a small mixed file to follow it consistently.

Official documentation

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