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:
interfacecan achieve similar results withextends(see below), but the syntax and semantics differ slightly from intersections.
Core differences (detailed)
1. Declaration merging
-
interfacesupports 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 }
-
typealiases cannot be reopened or merged. You’ll get an error if you try to redeclare the sametypename.
2. Expressiveness
-
typecan represent unions, intersections, primitives, tuples, mapped types, conditional types, and function signatures. -
interfaceis mainly for object shapes (and callable/construct signatures), though it can represent function shapes and be extended.
3. extends vs & (extends vs intersection)
-
interfaceusesextendsto inherit from other interfaces:
interface A { a: number }
interface B { b: string }
interface C extends A, B { c: boolean }
-
typeuses 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
-
interfacesignals: this is an object shape / API surface / contract to implement. -
typesignals: 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
interfacewhen:
- 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
extendssyntax for composing object shapes and want a clear OOP-style contract.
-
Use
typewhen:
- 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
interfacefor objects andtypefor 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
interfacein many codebases.
Top comments (0)