skip to content

Interface vs Type Alias

The two declaration forms for naming a type, and the real differences between them: merging, extension syntax, and what each one can express. This is the single most-asked TypeScript question, and a good answer is about capability and API design rather than personal preference.

part ofTypeScriptoverview, primer and where to startread it →
on this pageshow

questions

14

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

open as a page

In TypeScript, what happens when the same interface name is declared twice in one scope, and what happens when the same `type` alias name is declared twice?

level: juniorimportance: must knowfreq 72%

basics

~20 s

Two interface declarations sharing a name in the same scope merge into one type carrying both sets of members. Repeating a type alias name is a duplicate-identifier error instead: interfaces are open, aliases are closed.

open as a page

In TypeScript, both `interface B extends A { id: number }` and `type B = A & { id: number }` compose A's members with a new one. If A already declares `id: string`, how do the two forms behave differently?

level: middleimportance: must knowfreq 66%

basics

~20 s

extends overrides the inherited member and errors right at the declaration when the new type is not assignable to the old one. An intersection instead combines both types, so id becomes string & number, which reduces to never — a failure you only meet at the use site.

open as a page

In TypeScript, which kinds of `type` alias can an `interface` extend in its `extends` clause, and which ones does the compiler reject?

level: juniorimportance: should knowfreq 44%

basics

~20 s

An interface can extend an alias that resolves to an object type, or to an intersection of object types, as long as its members are statically known. Aliases for unions, primitives or unresolved type parameters are rejected.

open as a page

In TypeScript, a value typed by `interface Config { url: string }` is rejected when passed to a parameter of type `Record<string, unknown>`, while the same value typed by `type Config = { url: string }` is accepted. Why does the choice of declaration form change that?

level: middleimportance: should knowfreq 38%

basics

~20 s

Object-literal type aliases receive an implicit index signature; interfaces do not, because an interface can gain members from a later declaration, so the compiler will not treat its member list as final. Add an explicit index signature or use an alias.

open as a page

In TypeScript, how do you add a typed property to the DOM `Window` interface from inside a module file, and what does the `declare global` block do there?

level: middleimportance: should knowfreq 55%

basics

~20 s

Wrap the declaration in a declare global block inside a module file: it reopens the global declaration space so interface Window { ... } merges with the DOM's Window instead of creating a new module-local interface.

open as a page

In TypeScript, how do you add a property to an interface that an npm package exports, and what rules must the `declare module 'pkg'` block obey?

level: middleimportance: should knowfreq 48%

basics

~20 s

Write a module augmentation: in a file that is itself a module, a declare module 'pkg' block re-declares one of the package's exported interfaces, and the members merge into the original. It can only patch existing declarations.

open as a page

You ship a TypeScript library whose published types include `interface RequestOptions`. What can consumers do with that declaration that they could not if you had exported it as a type alias, and why might you not want them to?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Consumers can add members to an exported interface from their own code by redeclaring it in a module augmentation; an exported type alias is closed and a second declaration is an error. That extensibility is a public commitment you cannot easily withdraw.

open as a page

In TypeScript, when two same-name interface declarations each declare a member called `format`, when is that a compile error and when do you get an overload set?

level: seniorimportance: should knowfreq 36%

basics

~20 s

Method-syntax members of the same name become an overload set when interfaces merge. Ordinary properties, including function-typed ones, must be unique or declared with an identical type — otherwise the compiler reports that subsequent property declarations must have the same type.

open as a page

In TypeScript, does swapping the operands of an intersection type — writing `B & A` instead of `A & B` — ever change how the resulting type behaves?

level: seniorimportance: should knowfreq 30%

basics

~20 s

For property members, no: each member's type is the intersection of both contributions, and that is commutative, so the two orderings are mutually assignable. Order does matter for call and construct signatures, which are concatenated in operand order and resolved first-match-wins.

open as a page

In TypeScript, what does declaring a `namespace` with the same name as an existing interface, class, or function give you, and what ordering rule applies?

level: middleimportance: nice to knowfreq 30%

basics

~20 s

The namespace merges with the other declaration, so one name serves as both a type and a container of values — a companion for an interface, or static members hung on a class, function or enum. The namespace must come after the declaration it merges with.

open as a page

In TypeScript an interface may extend a class, as in `interface Renderable extends Widget { label: string }`. What does the interface inherit from the class, and what restricts which types can implement it?

level: middleimportance: nice to knowfreq 26%

basics

~20 s

The interface inherits the class's instance-side members — property and method types, without any implementations, and without the constructor or statics. It also inherits private and protected members, and because those are nominal, only the class itself or a subclass can implement the interface.

open as a page

Your organisation wants a single house rule for `interface` versus `type` across a large TypeScript codebase. What rule would you set, where must it allow exceptions, and how much is the consistency actually worth?

level: principalimportance: nice to knowfreq 28%

basics

~20 s

Set a default rather than a mandate: interface for declared object shapes, type alias for everything else, with language-forced exceptions for unions, tuples, primitives and derived types. Apply it to new code only, and reserve real review attention for published, extensible surfaces.

open as a page

Your team proposes augmenting a third-party library's exported interface in TypeScript so a field it does not declare becomes visible everywhere. What do you weigh before accepting that over defining a local type?

level: principalimportance: nice to knowfreq 26%

basics

~20 s

Augmentation is program-wide, unconditional and unenforced: every file sees the field whether or not it imports yours, two packages adding the same name collide hard, and the type promises something no runtime code checks. Prefer a local type unless third-party code must see the change.

open as a page