TypeScript

Composite Types in TypeScript – type vs interface with Real Examples

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:

  • type aliases
  • interface declarations

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

Featuretypeinterface
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 toolingGoodSlightly 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

  • type and interface both 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 interface in Angular and type in React, but both are valid.
  • In Node.js / backend code, generics with ApiResponse<T> are a safe alternative to returning unknown.
  • Best practice :
    • Use interface for objects and contracts (models, props).
    • Use type for unions, intersections, and utility types.
    • Use generics for API responses instead of unknown.

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

Featuretypeinterface
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 toolingGoodSlightly better in large OOP codebases
Preferred in librariesOften interfaceHeavily used in frameworks like Angular

5. When to Use type

Use type when:

  1. Defining unions and intersections type Status = "loading" | "success" | "error"; type ApiResult = { status: Status } & { data?: unknown }; 👉 interface cannot do this.
  2. Typing primitives and aliases type UUID = string; type Count = number;
  3. Describing tuples type Point = [number, number];
  4. Creating utility/conditional types type Nullable<T> = T | null; type ApiResponse<T> = { success: true; data: T } | { success: false; error: string };
  5. 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:

  1. Defining object shapes and contracts interface User { id: number; name: string; }
  2. Describing class contracts interface Shape { area(): number; } class Circle implements Shape { constructor(public r: number) {} area() { return Math.PI * this.r * this.r; } }
  3. 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.
  4. Angular services & components
    Angular’s ecosystem favors interface for models: interface Product { id: string; name: string; } @Component({...}) export class ProductCard { @Input() product!: Product; }
  5. 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 type for props.
  • In Angular/Vue/enterprise Node.js → prefer interface for models.
  • Best practice: Don’t pick one globally — use both where they’re strongest.

Related posts
TypeScript

Literals in TypeScript: String, Number, Boolean, Template, and Compound Literals

Literals are one of the fundamental building blocks of TypeScript programming. They represent fixed…
Read more
TypeScript

Utility Types in TypeScript: Pick, Omit, Partial, Required, Readonly, Record...

✅ — Utility Types are one of the most powerful features of TypeScript, but also one of the most…
Read more
TypeScript

Complete TypeScript Tutorial Online: Master TypeScript in 2025

TypeScript has revolutionized modern web development by bringing static typing to JavaScript, making…
Read more