TypeScript's type vs. interface: The Real Difference Is Inheritance vs. Type Algebra
Type vs Interface: After Reading This, No Interviewer Can Stump You
Introduction
In TypeScript, type and interface are the two most frequently used keywords in daily development. Both can describe the structure of an object, but their underlying mechanisms are fundamentally different. This is the TypeScript question interviewers love to ask most, nine times out of ten. This article will start from a practical React function component scenario, combining notes and code to thoroughly explain their similarities and differences.
1. Common Ground: Both Can Describe the "Shape of an Object"
Let's look at a basic piece of code first:
// interface syntax
interface User {
name: string;
age: number;
avatarUrl: string;
}
// type syntax
type UserType = {
name: string;
age: number;
avatarUrl: string;
};
const u1: User = { name: "Zhang San", age: 18, avatarUrl: "https://example.com/avatar.jpg" };
const u2: UserType = { name: "Li Si", age: 20, avatarUrl: "https://example.com/avatar.jpg" };
What they have in common: Both interface and type can:
- Describe the structure of an object—what properties it contains and the type of each property
- Be used for type annotations on function parameters
- Be used for function return types
- Constrain the types of variables and objects
At the level of describing object shapes, they have different syntax but equivalent capabilities.
2. Difference 1: Completely Different Inheritance Mechanisms
This is a fundamental difference—the way the two implement "inheritance/composition" is entirely distinct.
interface Person {
name: string;
}
// interface uses extends for inheritance, like Java's OOP
interface Employee extends Person {
job: string;
}
// type uses intersection types & for composition
type PersonType = { name: string };
type EmployeeType = PersonType & { job: string };
const e1: Employee = { name: "Zhang San", job: "ByteDance Development Engineer" };
const e2: EmployeeType = { name: "Li Si", job: "Big Tech Hopeful" };
Key Understanding:
| interface | type | |
|---|---|---|
| Syntax | extends (Inheritance) |
& (Intersection Type) |
| Semantics | "I am a Person, and I also have a job" | "Merge all properties of PersonType with { job }" |
| Underlying Idea | Nominal typing mindset from interface-oriented programming | Structural operations at the type level |
Behind extends is TypeScript's homage to traditional OOP—it explicitly declares that Employee is a subtype of Person. &, on the other hand, is a type algebra operation; it doesn't care about "who is a subtype of whom," it simply takes the union of the property sets of two types.
3. Difference 2: Declaration Merging—interface's "Incremental Declaration" Capability
This is the most unique capability of interface and a high-frequency interview topic:
interface Animal {
name: string;
}
// Interfaces with the same name automatically merge!
interface Animal {
age: number;
}
const dog: Animal = { name: "Three-Inch Nail", age: 2 }; // ✅ Both properties are required
// What about type?
type AnimalType = { name: string };
type AnimalType = { age: number }; // ❌ Error! Duplicate identifier
Why does this happen?
The TypeScript compiler has special handling for interface—when it encounters declarations with the same name, it automatically merges the members rather than treating them as duplicate definitions. This is called Declaration Merging.
The value of this design lies in:
Scenario: You reference a third-party library's type definition and want to add properties without modifying the source code.
Third-party .d.ts:
interface Window { appName: string }
Your code:
interface Window { appVersion: number } // Merges, no error!
When used:
window.appName // ✅
window.appVersion // ✅
type cannot do this, because type is essentially a type alias—a reference to an existing type. Within the same scope, the same name cannot point to two different things, which is the same reason const cannot be redeclared.
4. Difference 3: Things type Can Represent That interface Cannot
This is where type is irreplaceable—it can describe non-object types:
// Union types — interface cannot do this
type ID = string | number;
// Tuple types — interface cannot do this
type Point = [number, number];
// Try with interface?
interface ID {} // Empty, completely unable to express the semantics of "string or number"
What interface can express What type can express
┌─────────────────┐ ┌───────────────────────┐
│ Object shapes │ │ Object shapes │
│ { a: string } │ │ { a: string } │
│ │ │ │
└─────────────────┘ │ Union types string|number │
│ Tuples [number,number]│
│ Literals 'a'|'b' │
│ Mapped types, etc... │
└───────────────────────┘
Understanding the essence: type is a type alias; it can point to any type expression—object types, union types, tuples, primitive types, literal types... whereas interface is designed to define the contract of an object; it can inherently only describe object shapes.
5. Difference 4: Function Types—Both Can Write Them, type Is More Concise
// interface defining a function type — requires writing a full method signature
interface AddFN {
(a: number, b: number): number;
}
const add1: AddFN = (x, y) => x + y;
// type defining a function type — as natural as an arrow function
type AddType = (a: number, b: number) => number;
Both can express function types, but type's syntax is closer to how functions are written daily, with a lower cognitive load. interface requires writing an object with a call signature—it still describes a function as a "callable object," a mindset inherited from OOP language families.
6. In Practice: Why interface Is Preferred in React Function Components
Back to real engineering. In React + TypeScript projects, component Props are almost always defined using interface:
// Interface — core traditional OOP concept: abstraction
// JS itself is prototypal, with functions as first-class citizens
// TS provides strong typing for large-scale enterprise development, with a mindset leaning towards traditional Java OOP
// class extends / implements interface
// Interface-oriented programming — the data contract between parent and child components
interface User {
name: string;
age: number;
avatarUrl: string;
}
interface UserCardProps {
user: User;
onEdit: (id: number) => void;
}
const UserCard: React.FC<UserCardProps> = ({ user, onEdit }) => {
return (
<div>
<img src={user.avatarUrl} alt={user.name} />
<span>{user.name} · {user.age} years old</span>
<button onClick={() => onEdit(user.id)}>Edit</button>
</div>
);
};
Why choose interface for function component Props?
Semantic Alignment: Props are inherently a "contract"—the parent component says "I provide this data," and the child component says "I need this data."
interface'sextendsinheritance aligns perfectly with OOP interface programming, expressing exactly this "contract" concept.Extensibility: Props defined with
interfacecan be extended usingextendsto create more specific variants, which naturally matches polymorphic component patterns:interface BaseCardProps { user: User } interface AdminCardProps extends BaseCardProps { permissions: string[] }Declaration Merging: For third-party component library Props types, you can seamlessly extend them using
interface's same-name merging capability without modifying the library code.Official and Community Convention: React's own type definitions (
React.FC,React.ComponentProps, etc.) heavily useinterface, and the component Props of mainstream community projects (Ant Design, Material UI) are uniformlyinterface. Following the community maximizes readability.
7. Summary in One Diagram
Selection Guide
│
┌────────────────┼────────────────┐
│ │ │
Need to describe Need union/tuple? Unsure?
object shape? │ │
│ │ │
┌────▼────┐ ┌────▼────┐ ┌────▼────┐
│interface│ │ type │ │interface│
│ │ │ │ │ (default)│
│ React │ │ Utility │ │ More │
│ Props │ │ types, │ │ extensible│
│ Contract│ │ flexible│ │ and │
│ Design │ │ composition│ │ friendly │
└─────────┘ └─────────┘ └─────────┘
Remember in one sentence:
interfacedefines a "contract" (what an object can do),typecreates an "alias" (what this thing is called). Useinterfacefor component Props, usetypefor utility types.
The two are both complementary and overlapping. Understanding their fundamental differences allows you to clearly state "why I chose this one here" during code review—rather than just "I saw everyone else write it this way."