Interface vs Type in TypeScript: What Is the Difference?
In TypeScript, both interfaces and type aliases define the shape of an object or data structure, but they serve distinct purposes. Interfaces excel at defining extensible object contracts and object-oriented architectures through declaration merging and inheritance. Type aliases offer broader flexibility by expressing primitives, unions, intersections, tuples, and mapped types. Understanding their technical nuances ensures clean, maintainable, and idiomatic TypeScript codebases.
Core Capabilities and Syntax
The most fundamental distinction between the two constructs lies in what kinds of types they can declare.
An interface is strictly limited to declaring object
shapes and function signatures. It cannot directly name primitive types,
literal types, or union expressions.
interface UserProfile {
id: string;
name: string;
roles: string[];
}A type alias can define object shapes, but it can also
represent primitives, unions, tuples, and computed types.
type ID = string | number;
type Status = "pending" | "approved" | "rejected";
type Coordinate = [number, number];
type ResponseData = UserProfile | null;Declaration Merging
Declaration merging is unique to interfaces. When multiple interfaces share the same identifier within the same scope, TypeScript automatically merges their declarations into a single combined definition.
interface Window {
customAnalyticsId: string;
}
interface Window {
isFeatureEnabled(featureName: string): boolean;
}
// Window now contains both customAnalyticsId and isFeatureEnabledType aliases do not support declaration merging. Attempting to redeclare a type alias with an existing name results in a duplicate identifier compile-time error.
type Config = { port: number };
// Error: Duplicate identifier 'Config'
type Config = { host: string }; Declaration merging makes interfaces indispensable for public libraries and DefinitelyTyped packages, allowing downstream consumers to safely augment global namespaces or library types.
Extension and Composition Mechanics
Both constructs allow combining definitions, but their syntax and inheritance behaviors differ.
Interfaces extend other interfaces or object-based types using the
extends keyword. During compilation, TypeScript verifies
that property signatures are compatible. If a sub-interface attempts to
redeclare a property with an incompatible type, the compiler issues an
immediate error.
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}Type aliases combine definitions using intersection operators
(&). Unlike interface extension, intersection combines
property types directly. If overlapping properties share incompatible
types (such as string and number), the
resulting property collapses to never rather than failing
during the type definition phase.
type BaseItem = { id: string };
type PricedItem = BaseItem & { price: number };Compiler Performance Differences
The TypeScript compiler handles interfaces and type aliases differently during type-checking:
- Caching: The compiler caches the shapes of named interfaces, allowing faster lookups when checking type compatibility across large projects.
- Relationship Checking: Interface-to-interface
relationships (via
extends) use structured inheritance trees, reducing the overhead of evaluating complex property intersections repeatedly. - Intersection Resolution: Complex nested intersections using type aliases require the compiler to compute cross-products of properties across all branches, which can degrade build performance in massive enterprise codebases.
Choosing Between Interfaces and Types
A practical approach to choosing between the two constructs includes:
- **Use an
interface**when defining public API boundaries, writing library definitions, modeling object-oriented class implementations viaimplements, or requiring declaration merging. - Use a
typealias when working with union types, intersection helpers, tuples, primitives, function type shorthand, or complex mapped and conditional types.