Composite types (type and interface) are the bread and butter of TypeScript ✅ for structuring complex data, especially in modern frameworks (React, Angular, Vue, Node.js). Let’s consider this topic in-depth with examples across different frameworks.
TypeScript extends JavaScript with a type system that allows us to describe the shape of objects, functions, and APIs.
The two most common tools for building composite types (types made of multiple fields/methods) are:
typealiasesinterfacedeclarations
They look similar but have subtle differences. Let’s break it down.
1. type in TypeScript
A type alias allows you to give a name to any type: primitives, unions, intersections, tuples, functions, or objects.
type User = {
id: number;
name: string;
isAdmin?: boolean; // optional
};
const user: User = { id: 1, name: "Alice" };
👉 Best for: unions, intersections, utility types, and when combining multiple types.
2. interface in TypeScript
An interface defines the shape of an object (properties, methods). It is extendable (supports declaration merging).
interface User {
id: number;
name: string;
isAdmin?: boolean;
}
const user: User = { id: 1, name: "Bob" };
👉 Best for: describing objects, especially when you expect extensions (e.g., libraries or frameworks).
3. Similarities Between type and interface
- Both can describe objects:
type Car = { brand: string; speed: number };
interface Bike { brand: string; speed: number }
- Both support readonly and optional properties.
- Both can describe function signatures:
type Add = (a: number, b: number) => number;
interface Multiply {
(a: number, b: number): number;
}
4. Differences Between type and interface
| Feature | type | interface |
|---|---|---|
| Can describe objects | ✅ Yes | ✅ Yes |
| Can describe primitives | ✅ Yes | ❌ No |
| Can describe unions/intersections | ✅ Yes | ❌ No |
| Declaration merging | ❌ No | ✅ Yes |
| Extending types | ✅ With intersections (&) | ✅ With extends |
| Readability in tooling | Good | Slightly better in large codebases |
5. Composite Types in React (Props Example)
In React, both type and interface are used to describe component props.
Using type
type ButtonProps = {
label: string;
onClick: () => void;
disabled?: boolean;
};
const Button: React.FC<ButtonProps> = ({ label, onClick, disabled }) => (
<button disabled={disabled} onClick={onClick}>{label}</button>
);
Using interface
interface ButtonProps {
label: string;
onClick: () => void;
disabled?: boolean;
}
const Button = ({ label, onClick, disabled }: ButtonProps) => (
<button disabled={disabled} onClick={onClick}>{label}</button>
);
👉 Both approaches work — type is often preferred for flexibility, while interface is common in enterprise teams for extensibility.
6. Composite Types in Angular
Angular uses interfaces a lot for typing data models and component inputs.
interface User {
id: number;
name: string;
}
@Component({
selector: "app-user-card",
template: `<div>{{ user.name }}</div>`
})
export class UserCard {
@Input() user!: User;
}
7. Composite Types in Vue 3 (with TypeScript)
With Vue’s Composition API, you can use type or interface for props:
type User = { id: number; name: string };
export default defineComponent({
props: {
user: { type: Object as PropType<User>, required: true }
},
setup(props) {
console.log(props.user.name);
}
});
8. Composite Types in Node.js (API Responses)
When building APIs, describing response structures is crucial.
interface ApiResponse<T> {
success: boolean;
data: T;
error?: string;
}
// Example usage
type User = { id: number; name: string };
function getUser(): ApiResponse<User> {
return { success: true, data: { id: 1, name: "Alice" } };
}
Here we used generics instead of unknown. Instead of returning ApiResponse<unknown>, we define generic T, ensuring type safety per endpoint.
9. Functions with Composite Types
Both type and interface can describe function shapes:
type FetchFn = (url: string) => Promise<string>;
interface SaveFn {
(data: object): Promise<void>;
}
10. Generics Instead of unknown (Best Practice with Backend Data)
When working with backend APIs, instead of returning unknown, we can use generics:
interface ApiResponse<T> {
status: number;
data: T;
}
async function fetchJson<T>(url: string): Promise<ApiResponse<T>> {
const res = await fetch(url);
return { status: res.status, data: await res.json() as T };
}
// Usage
type User = { id: number; name: string };
const userResponse = await fetchJson<User>("/api/user");
console.log(userResponse.data.name); // ✅ type-safe
👉 This way, you don’t need to cast from unknown each time — you enforce the expected structure upfront.
Conclusion
typeandinterfaceboth define composite types, but differ in flexibility (type→ unions, primitives, intersections;interface→ declaration merging, extends).- In React, Angular, Vue, both are used for props and models. Teams often prefer
interfacein Angular andtypein React, but both are valid. - In Node.js / backend code, generics with
ApiResponse<T>are a safe alternative to returningunknown. - Best practice :
- Use
interfacefor objects and contracts (models, props). - Use
typefor unions, intersections, and utility types. - Use generics for API responses instead of
unknown.
- Use
Perfect — this is one of the most debated topics in TypeScript: type vs interface.
Developers often ask: “Which should I use?” The answer is: both are valuable — but each has strengths and weaknesses.
Here’s a deep-dive unique explanation with real-world examples and clear rules of when to use type vs interface.
Type vs Interface in TypeScript – Complete Guide with Real Examples
TypeScript provides two ways to define composite types:
type(type alias)interface(object contract)
At first glance, they look similar — both can describe objects, functions, classes.
But under the hood, they have different capabilities and best use cases.
1. What is a type?
A type alias gives a name to any type.
It can represent primitives, objects, unions, intersections, tuples, functions, or generics.
type UserID = string | number; // primitive + union
type User = {
id: UserID;
name: string;
isAdmin?: boolean;
};
👉 Think of type as a flexible label for any possible type construct.
2. What is an interface?
An interface defines the shape of an object or class.
It is best for describing contracts in OOP style.
interface User {
id: number;
name: string;
isAdmin?: boolean;
}
👉 Think of interface as a blueprint for objects.
3. Similarities Between type and interface
Both can:
- Describe objects:
type A = { id: number }; interface B { id: number } - Describe functions:
type Add = (a: number, b: number) => number; interface Multiply { (a: number, b: number): number } - Extend other types:
type T1 = { a: number }; type T2 = T1 & { b: number }; // intersection interface I1 { a: number } interface I2 extends I1 { b: number }
4. Key Differences Between type and interface
| Feature | type | interface |
|---|---|---|
| Can describe primitives | ✅ Yes (type Age = number) | ❌ No |
| Can describe unions/intersections | ✅ Yes | ❌ No |
| Declaration merging | ❌ No | ✅ Yes (can reopen same name) |
| Extending multiple | ✅ With intersections & | ✅ With extends |
| Readability in tooling | Good | Slightly better in large OOP codebases |
| Preferred in libraries | Often interface | Heavily used in frameworks like Angular |
5. When to Use type
✅ Use type when:
- Defining unions and intersections
type Status = "loading" | "success" | "error"; type ApiResult = { status: Status } & { data?: unknown };👉interfacecannot do this. - Typing primitives and aliases
type UUID = string; type Count = number; - Describing tuples
type Point = [number, number]; - Creating utility/conditional types
type Nullable<T> = T | null; type ApiResponse<T> = { success: true; data: T } | { success: false; error: string }; - React Props (preferred in many teams)
type ButtonProps = { label: string; onClick?: () => void; }; const Button = ({ label, onClick }: ButtonProps) => <button>{label}</button>;
❌ Avoid type when:
- You need declaration merging (extending same type name across files).
- You are working in OOP-heavy projects (like Angular services or class-based patterns).
6. When to Use interface
✅ Use interface when:
- Defining object shapes and contracts
interface User { id: number; name: string; } - Describing class contracts
interface Shape { area(): number; } class Circle implements Shape { constructor(public r: number) {} area() { return Math.PI * this.r * this.r; } } - Declaration merging (extending existing interfaces)
interface Window { customProp?: string } interface Window { anotherProp?: number } // Now Window has both props! window.customProp = "hello";👉 Great for augmenting third-party libraries. - Angular services & components
Angular’s ecosystem favorsinterfacefor models:interface Product { id: string; name: string; } @Component({...}) export class ProductCard { @Input() product!: Product; } - Vue/Angular/React contracts in teams
Many enterprise projects enforce interfaces for consistency, especially in API models.
❌ Avoid interface when:
- You need union or intersection types.
- You need tuples, conditional types, or mapped types.
7. Real-World Examples
(a) Node.js API Response (type with generics)
type ApiResponse<T> = { success: true; data: T } | { success: false; error: string };
async function fetchUser(): Promise<ApiResponse<User>> {
return { success: true, data: { id: 1, name: "Alice" } };
}
(b) React Props (type or interface – both valid)
// With type
type ButtonProps = { label: string; onClick?: () => void };
// With interface
interface ButtonProps { label: string; onClick?: () => void }
👉 Many React teams prefer type because props often include unions and intersections.
(c) Extending Third-Party Types (interface)
// Suppose a library defines
interface Request { url: string }
// You can augment:
interface Request { token?: string }
function handle(req: Request) {
console.log(req.url, req.token);
}
👉 You cannot do this with type.
(d) Mixed: type + interface Together
Best practice: combine both where they shine.
interface User {
id: number;
name: string;
}
type ApiResponse<T> = { success: true; data: T } | { success: false; error: string };
function getUser(): ApiResponse<User> {
return { success: true, data: { id: 1, name: "Alice" } };
}
8. Decision Guide
Use type when:
- You need unions, intersections, tuples, primitives, utility types.
- You are working with React props.
- You want concise aliases.
Use interface when:
- You are defining object shapes or class contracts.
- You need declaration merging (e.g., library augmentation).
- You work in OOP-heavy frameworks (Angular, enterprise apps).
9. Conclusion⚡
type= flexible: unions, intersections, primitives, tuples, utility types.interface= structured: object shapes, class contracts, declaration merging.- In React → prefer
typefor props. - In Angular/Vue/enterprise Node.js → prefer
interfacefor models. - ✅ Best practice: Don’t pick one globally — use both where they’re strongest.