What you'll learn
Quick Answer
Use interface for object shapes and public APIs — it reads cleanly, supports declaration merging, and is easy to extend. Use type when you need unions, tuples, primitives, or function and mapped types, which interface cannot express. For most everyday objects either works, so pick one and stay consistent. When you are unsure about an object, reach for interface first.
The short version
If you have written any TypeScript, you have seen two ways to describe the shape of your data: interface and type. They overlap so much that the typescript interface vs type question trips up almost every beginner. The honest answer is this: for plain object shapes, they are nearly interchangeable. The real differences show up at the edges.
This guide walks through those edges — declaration merging, extending, unions, and object shapes — with side-by-side examples you can paste straight into the TypeScript Playground. At the end you get one simple rule you can follow without overthinking it.
Both can describe an object's shape
Start with the thing they both do well: describing an object.
// As an interface
interface User {
id: number;
name: string;
isActive: boolean;
}
// As a type alias
type UserT = {
id: number;
name: string;
isActive: boolean;
};Both versions are enforced identically. A function that takes a User and a function that takes a UserT behave the same way, and TypeScript complains in exactly the same places.
function greet(user: User) {
return `Hello, ${user.name}`;
}If your whole codebase only ever described object shapes, you could pick either keyword and never notice a difference. So let's look at where they part ways.
Interface vs type: side-by-side
Here is the quick map before we dig into each row.
| Can it… | interface | type |
|---|---|---|
| Describe an object shape | Yes | Yes |
| Merge two declarations of the same name | Yes | No |
| Extend / combine shapes | Yes | Yes |
| Represent a union (A or B) | No | Yes |
| Alias a primitive, tuple, or function | Partial | Yes |
| Build mapped & conditional types | No | Yes |
Be used with a class implements | Yes | Partial |
"Partial" for an interface aliasing a function is because interfaces can describe a callable shape but cannot alias a bare primitive like number. "Partial" for type with implements is because a class can implement a type alias only when it is an object shape, not a union. We cover both below.
Declaration merging: only interface can do it
This is the one real superpower interface has. If you declare the same interface name twice, TypeScript merges them into a single interface.
interface Book {
title: string;
}
interface Book {
author: string;
}
// Book is now { title: string; author: string }
const b: Book = {
title: "Wings of Fire",
author: "A.P.J. Abdul Kalam",
};Try the same with type and the compiler stops you:
type Book = { title: string };
type Book = { author: string };
// Error: Duplicate identifier 'Book'.Why care? Merging is how you add properties to shapes you do not own — for example, extending the global Window object or augmenting a third-party library's types. That is a real, if occasional, need.
The flip side: merging can happen by accident. Two contributors define interface Config in the same scope and the shapes silently combine instead of raising a "name already used" error. With type, that mistake is caught immediately.
Extending and combining shapes
Both keywords can build a bigger shape from a smaller one; they just use different syntax.
// interface uses `extends`
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}
// type uses an intersection `&`
type AnimalT = { name: string };
type DogT = AnimalT & { breed: string };They even mix. An interface can extend a type alias, and a type can intersect an interface:
type Timestamps = { createdAt: Date };
interface Post extends Timestamps {
body: string;
}One meaningful difference is what happens on a conflict. If you extend an interface but give a property an incompatible type, you get a clear error:
interface A { x: number }
interface B extends A { x: string }
// Error: Interface 'B' incorrectly extends 'A'.With an intersection there is no error — the conflicting property quietly becomes never, which you may not notice until something fails later:
type A2 = { x: number };
type B2 = { x: string };
type C2 = A2 & B2;
// C2 is { x: never } — impossible to satisfy
Unions, tuples, and primitives: only type can do it
type is the more flexible keyword. It can name things an interface simply cannot express.
// Union: a value that is one of several options
type Status = "active" | "inactive" | "banned";
type ID = string | number;
// Tuple: a fixed-length, ordered array
type Point = [number, number];
// Primitive alias
type Age = number;
// Function type
type Formatter = (value: number) => string;None of those can be written as an interface. An interface can describe a callable object:
interface Formatter {
(value: number): string;
}…but it cannot be a union, a tuple, or a plain alias for number. type also unlocks mapped and conditional types, which power a lot of TypeScript's built-in utility types:
// Make every property optional
type Partialize<T> = {
[K in keyof T]?: T[K];
};If you need any of these — unions especially — the choice is made for you: use type.
Gotchas worth knowing
A few surprises that catch people out.
Neither exists at runtime
Both interfaces and type aliases are erased during compilation. The JavaScript you ship has no trace of them, so the choice never affects the performance of your running app — only your developer experience.
Interfaces and index signatures
An interface is not automatically assignable to an index-signature type, but an equivalent type alias is:
interface Scores {
math: number;
}
type Lookup = { [subject: string]: number };
const s: Scores = { math: 90 };
const table: Lookup = s;
// Error with the interface versionThe reason is declaration merging: because someone could later add a non-number property to Scores, TypeScript cannot promise every value is a number. A closed type carries no such risk.
Performance at scale
For very large codebases, the TypeScript team notes that extends on interfaces can be a little friendlier to the compiler's caching than long chains of intersections. On everyday projects you will never notice, so do not choose based on this alone.
A rule of thumb for everyday code
Put it all together and you get a rule you can apply on autopilot:
- Use
interfacefor object shapes — component props, API responses, class contracts, and anything others might extend. It reads cleanly and gives nice error messages. - Use
typewhen you need more than an object — unions, tuples, primitives, function signatures, or mapped and conditional types. - When in doubt on an object, reach for
interfacefirst. You can always switch later; for plain shapes they are easy to swap.
The most important rule is bigger than either keyword: be consistent. A codebase where everyone follows the same convention is far easier to read than one that mixes both at random.
Want to practice this hands-on, from types all the way to real projects? Our free TypeScript course walks through interfaces, types, generics, and more with exercises built for beginners.
Frequently Asked Questions
Is interface faster than type?
At runtime, no — both are erased when TypeScript compiles to JavaScript, so your app runs identically either way. Inside the compiler, extends on interfaces can cache slightly better than long intersection chains in very large projects, but for normal codebases the difference is not something you will ever feel.
Can a class implement a type alias?
Yes, as long as the type alias describes an object shape. A class can implements both an interface and an object type alias. What you cannot implement is a type alias that is a union, because a class needs one fixed shape to satisfy.
Which should I use for React component props?
Either works and both are common. Many teams use type for props because props often involve unions and it composes neatly, while others prefer interface for its clean extend syntax. Pick one per project and stay consistent — that matters more than the keyword itself.
Can I switch an interface to a type later?
For plain object shapes, usually yes — you can rewrite interface Foo { ... } as type Foo = { ... } with little friction. The switch gets harder if you relied on declaration merging (interface only) or need a union (type only), since those features have no direct equivalent on the other side.
Do interface and type change the compiled JavaScript?
No. TypeScript types of every kind are removed during compilation. Interfaces and type aliases produce zero JavaScript output, so they never add to your bundle size or affect runtime behavior — they exist purely to help you and your editor catch mistakes early.
