A clear, practical guide to the difference between type
aliases and interface
in TypeScript — when to use which, what you can (and can't) express with each, and the most common operations you’ll perform (including unions and intersections).
Quick summary
-
interface
: primarily describes the shape of objects (and classes). Supports declaration merging,extends
, and is favored for public object APIs and OOP-style code. -
type
: a more general alias that can name object shapes and primitives, unions, tuples, mapped/conditional types, function signatures, etc. It cannot be reopened (no declaration merging).
Both are largely interchangeable for plain object shapes, but each has unique strengths.
Step 7 - Types (what are types?)
type
lets you aggregate data together similar to interface
, but it can also do other things.
type User = {
firstName: string;
lastName: string;
age: number;
}
1. Unions
If you need a value that can be one of several types (e.g. a user id that can be a number
or a string
), type
is the tool for that. Interfaces do not support union types directly.
type StringOrNumber = string | number;
function printId(id: StringOrNumber) {
console.log(`ID: ${id}`);
}
printId(101); // ID: 101
printId("202"); // ID: 202
2. Intersection
You can compose multiple object shapes into one by intersecting types. This creates a type that must satisfy all parts.
type Employee = {
name: string;
startDate: Date;
};
type Manager = {
name: string;
department: string;
};
type TeamLead = Employee & Manager;
const teamLead: TeamLead = {
name: "harkirat",
startDate: new Date(),
department: "Software developer"
};
Note:
interface
can achieve similar results withextends
(see below), but the syntax and semantics differ slightly from intersections.
Core differences (detailed)
1. Declaration merging
-
interface
supports declaration merging. If you declare the same interface name twice, TypeScript will merge the members.
interface Box { width: number }
interface Box { height: number }
// Box is now { width: number; height: number }
-
type
aliases cannot be reopened or merged. You’ll get an error if you try to redeclare the sametype
name.
2. Expressiveness
-
type
can represent unions, intersections, primitives, tuples, mapped types, conditional types, and function signatures. -
interface
is mainly for object shapes (and callable/construct signatures), though it can represent function shapes and be extended.
3. extends
vs &
(extends vs intersection)
-
interface
usesextends
to inherit from other interfaces:
interface A { a: number }
interface B { b: string }
interface C extends A, B { c: boolean }
-
type
uses intersections to compose types:
type A = { a: number }
type B = { b: string }
type C = A & B & { c: boolean }
Semantically they often produce the same resulting shape, but extends
participates in declaration merging and some structural checks differently.
4. Implementing in classes
Both can be used for implements
in classes, but interface
is most idiomatic:
interface Flyable { fly(): void }
class Bird implements Flyable {
fly() { console.log('flap') }
}
You can also implements
a type
that describes a callable or object shape, but interface
communicates intent more clearly for class contracts.
5. Readability and intent
-
interface
signals: this is an object shape / API surface / contract to implement. -
type
signals: an alias — could be complex (tuple/union/primitive/etc.)
This is stylistic, but matters in team codebases.
Examples and edge-cases
Using type
for primitives/unions/tuples
type ID = string | number;
type Point = [number, number];
Using interface
for objects and declaration merging
interface Config { url: string }
interface Config { timeout?: number }
// Config has both url and timeout now
Function type: two ways
// type alias
type Fn = (x: number) => string;
// interface (call signature)
interface FnInterface { (x: number): string }
Mapped & conditional types (only type
)
type Keys = 'a' | 'b';
type Mapped = { [K in Keys]: number };
type Conditional<T> = T extends string ? string[] : number[];
When to use which — practical guidelines
-
Use
interface
when:
- You’re describing public object shapes or library APIs.
- You want to allow declaration merging (e.g., augmenting global interfaces or merging the same interface across modules).
- You prefer
extends
syntax for composing object shapes and want a clear OOP-style contract.
-
Use
type
when:
- You need unions, tuples, primitives, mapped types, or conditional types.
- You want to alias a complex expression (e.g.,
Partial<Record<string, any>>
orstring | number
). - You want to build advanced generic utilities.
- When both work:
- For simple object shapes, pick whichever your team prefers. Many style guides prefer
interface
for objects andtype
for everything else.
Interoperability
In practice, type
and interface
often interoperate fine. You can extends
an interface
from a type
by first ensuring the type
is an object type, or you can intersect types with interfaces by using &
on the type side.
Short cheat-sheet
- Need union/tuple/primitive/mapped/conditional → use
type
. - Need declaration merging or an OOP-style contract → use
interface
. - Simple object shapes → either, prefer
interface
in many codebases.
Top comments (0)