skip to content

In TypeScript, which kinds of type can a `type` alias express that an `interface` declaration cannot, and how should that shape your choice between the two?

level: juniorimportance: must knowfreq 85%

answer

  1. alias names any type
  2. interface declares object shapes only
  3. no interface form for a union
  4. function types have both forms
  5. interface stays open, alias is closed

basics

~20 s

A type alias can name any type — unions, tuples, primitives and types derived from other types — while an interface declares an object type only. Reach for an alias whenever the type is not a plain object shape.

solid answer

~50 s

`type` is an alias: it binds a name to any type expression, so it is the only way to name a union like `"idle" | "busy"`, a tuple like `[number, number]`, a primitive alias like `type UserId = string`, a template literal type, or a type computed from another type. `interface` is a declaration form for object types — properties, methods, index signatures, call and construct signatures, plus `extends` — and there is simply no syntax for `interface X = A | B`. So the first branch of the decision is not taste at all: if the thing you are naming is not an object shape, an alias is your only option. When it *is* a plain object shape the two are near-equivalent, and the real question becomes whether you want the type to stay open for later extension, which is what an interface gives you and an alias does not.

code

typescript · 14 lines
typescript
type Status = "idle" | "busy";       // union: no interface form
type Pair = [x: number, y: number];  // tuple: no interface form
type UserId = string;                // primitive alias: no interface form

// A function type, however, has both forms:
type Formatter = (value: number) => string;
interface FormatterI {
  (value: number): string;
}

const f: Formatter = (v) => v.toFixed(2);
const g: FormatterI = f; // interchangeable

console.log(g(1.005));

go deeper

for a junior

Be able to say plainly that an interface only describes object shapes, so unions, tuples and primitive aliases must use type. Give one concrete example of each rather than reciting a rule.

for a middle

Explain that type binds a name to any type expression while interface declares an object type, and name the one real behavioural difference for plain shapes: an interface stays open to later declarations, an alias does not.

for a senior

Show that you decide by consequence, not habit: which types the language forces into an alias, and which exported shapes you deliberately leave open so downstream code can extend them. Mention that both erase, so nothing is at stake at runtime.

for a principal

Own the framing that this choice only becomes load-bearing at a published API boundary, and be ready to say what a house rule should mandate, what it must leave to the author, and why enforcing it on legacy code buys almost nothing.

## Two different declaration forms These are not two spellings of one feature. `type X = ...` is an **alias**: it binds a name to a type *expression*, and the expression may be anything the type grammar can produce. `interface X { ... }` is a **declaration of an object type**: it introduces a named type whose body is a member list. That asymmetry is the whole answer. An alias is a naming mechanism over the entire type language; an interface is one specific kind of type that happens to also get a name. ## What only an alias can name ```typescript type Status = "idle" | "busy" | "error"; // union type Point = [x: number, y: number]; // tuple type UserId = string; // primitive alias type Keys = keyof Point; // derived from another type type EventName = `on${"Click" | "Focus"}`; // template literal type ``` None of these has an interface form. There is no `interface Status = A | B`, and an interface body cannot hold a union, a tuple, or a bare primitive. The same applies to any type produced by transforming another type (those transformations are a topic of their own, but the *choice* consequence is the point here: a computed type must be an alias). ## What an interface can do Everything an object type can express: required and optional properties, `readonly` members, methods, index signatures, and `extends`. It is worth knowing that a *function* type is not alias-only — an interface can carry a call signature — so "functions need a type alias" is a common and wrong shortcut. Convention favours the alias form for a plain function type because it reads better, not because the interface form does not exist. ## The near-tie: a plain object shape For a straightforward record of properties, both forms produce a type that behaves the same way under structural checking: a value satisfying one satisfies the other, and neither creates a nominal barrier. Two differences survive: - **An interface is open.** Two declarations of the same interface name in the same scope combine rather than collide, and a consumer of your published types can add members to it from their own code. A second declaration of the same alias name is a duplicate-identifier error — the alias is closed once written. - **An object-literal alias gets an implicit index signature; an interface does not.** This is the openness fact seen from the assignability side: because an interface may gain members later, the compiler will not assume its member list is final. ## Self-reference Both forms can describe recursive data through object members and array elements: ```typescript type Tree = { value: number; children: Tree[] }; interface TreeI { value: number; children: TreeI[] } ``` What an alias cannot do is refer to itself *directly* in a position the compiler must resolve immediately — `type Loop = Loop | string` is rejected as a circular self-reference. In practice this almost never bites, because the useful recursive shapes go through a property or an array element. ## Neither exists at runtime Both declarations are erased during compilation and emit nothing. You cannot `instanceof` an interface, you cannot reflect over an alias, and the choice has zero effect on bundle size or runtime behaviour. Everything at stake is what the checker enforces and what a future reader and consumer can do. ## A decision rule you can say out loud 1. **Not an object shape?** Alias — there is no choice to make. Unions especially: the moment you model "one of these variants", you are writing a `type`. 2. **A type derived from another type?** Alias — again no choice. 3. **An exported object shape you want outside code to be able to extend?** Interface, deliberately. 4. **Anything else?** Either works; pick one and be consistent, because consistency is worth more than the marginal difference. The reason interviewers like this question is that a weak answer is a memorised style-guide line ("always use interface" / "always use type"), while a strong answer identifies the cases where the language removes the choice and the one case — the public, extensible surface — where the choice actually carries a commitment.

  • Does choosing an interface over a type alias change the emitted JavaScript?
    No. Both declarations are erased at compile time and emit nothing at all, so the choice cannot affect bundle size, startup cost or runtime behaviour. It is purely a question of what the checker enforces and what consumers of your types are able to do afterwards.
  • Can either form describe a recursive shape such as a tree node?
    Both can, as long as the self-reference goes through an object member or an array element — `type Tree = { value: number; children: Tree[] }` and the interface equivalent both compile. What an alias cannot do is reference itself directly, as in `type Loop = Loop | string`, which the compiler rejects as circular.
  • If you model a variant set as a union of object types, what do you give up compared with declaring interfaces?
    You give up openness: nobody, including you, can add a member to that union declaration later by redeclaring the name. The union itself must be a type alias, but each member can still be an interface, which is the usual compromise — extensible variants, a closed set.

saying these in an interview costs you the question

  • Says interface and type are completely interchangeable
  • Claims an interface body can hold a union with |
  • Thinks interfaces survive to runtime and support instanceof
  • Believes a type alias creates a new, incompatible type
  • Says function types can only be written as aliases

context