TypeScript

Unions vs Intersections in TypeScript – Complete Guide with Real Examples

This is one of the most important comparative topics in TypeScript: Unions vs Intersections.
Let’s create a full, unique article that contrasts them side by side with real-world examples from React, Angular, Vue, and Node.js.


TypeScript provides two essential type operators that often confuse developers:

  • Union (|)either one type or another.
  • Intersection (&)must satisfy all types at once.

Understanding when to use each will make your codebase more robust, readable, and type-safe.


1. Conceptual Difference

Think of sets in mathematics:

  • Union (|) = all elements from A and B.
  • Intersection (&) = only elements that belong to both A and B simultaneously.

2. Union Types (|)

A union allows a value to be one of several possible types.

type Id = string | number;

let userId: Id;

userId = "abc123"; // ✅ string
userId = 42;       // ✅ number
userId = true;     // ❌ not allowed

👉 Great when you have alternative possibilities.


3. Intersection Types (&)

An intersection combines multiple types into one.
The resulting type must satisfy all constraints.

type HasId = { id: number };
type HasName = { name: string };

type User = HasId & HasName;

const u: User = { id: 1, name: "Alice" }; // ✅ must have both

👉 Great when you want composition of features.


4. Comparison Table

| Feature | Union (|) | Intersection (&) |
|———|————-|———————|
| Meaning | Either type A or B | Must be type A and B |
| Flexibility | More flexible (choice) | More strict (combination) |
| Objects | Value can match any one shape | Value must include all properties |
| Functions | Accepts broader inputs | Requires multiple call signatures |
| Use Case | APIs returning multiple formats | Composing reusable models |


5. Real-World Examples

(a) React Props

Union – Flexible props (only one required)

type ButtonProps = 
  | { variant: "text"; onClick: () => void }
  | { variant: "link"; href: string };

const Button = (props: ButtonProps) => {
  if (props.variant === "text") {
    return <button onClick={props.onClick}>Click me</button>;
  }
  return <a href={props.href}>Go</a>;
};

👉 User can pass either onClick or href.


Intersection – Extending props

type BaseProps = { disabled?: boolean };
type ButtonProps = BaseProps & { label: string; onClick: () => void };

const Button = ({ label, disabled, onClick }: ButtonProps) => (
  <button disabled={disabled} onClick={onClick}>{label}</button>
);

👉 User must provide all required fields.


(b) Angular Models

Union – API response (success or error)

type ApiSuccess = { status: "success"; data: any };
type ApiError = { status: "error"; message: string };

type ApiResponse = ApiSuccess | ApiError;

function handleResponse(res: ApiResponse) {
  if (res.status === "success") {
    console.log(res.data);
  } else {
    console.error(res.message);
  }
}

Intersection – Entity + Timestamps

type Entity = { id: string };
type Timestamps = { createdAt: Date; updatedAt: Date };

type Product = Entity & Timestamps & { name: string };

(c) Vue with Composition API

Union – Prop with multiple allowed values

type Size = "small" | "medium" | "large";

export default defineComponent({
  props: {
    size: { type: String as PropType<Size>, required: true }
  }
});

Intersection – Combine mixins

type WithLoading = { loading: boolean };
type WithError = { error?: string };

type ComponentState = WithLoading & WithError;

(d) Node.js API Models

Union – Different request body shapes

type LoginBody = { email: string; password: string };
type RegisterBody = { name: string; email: string; password: string };

type AuthRequest = LoginBody | RegisterBody;

Intersection – Enriching database entities

type DbEntity = { id: string };
type User = { name: string; email: string };
type Auditable = { createdAt: Date; updatedAt: Date };

type UserEntity = DbEntity & User & Auditable;

6. Pitfalls

Union pitfalls

  • Too wide → TypeScript cannot narrow automatically.
  • Need type guards (e.g., if ("prop" in obj)).

Intersection pitfalls

  • Conflicting properties = type becomes never. type A = { x: string }; type B = { x: number }; type C = A & B; // ❌ x impossible → never

7. Best Practices

  • ✅ Use union for alternatives (API responses, variant props).
  • ✅ Use intersection for composition (adding features, combining entities).
  • ✅ Combine them for maximum expressiveness: type Admin = { role: "admin" } & (User | Moderator);
  • ❌ Avoid deeply nested unions/intersections → hurts readability.

8. Conclusion

  • Union (|) = choice (one of many).
  • Intersection (&) = combination (all at once).
  • Both are foundations of TypeScript’s type system.
  • Mastering them is key for writing robust React, Angular, Vue, and Node.js applications.

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