Union and Intersection Types in TypeScript

Learn how TypeScript union types let a value be one of several types, and how intersection types merge object shapes together — with clear, beginner-friendly examples.

Published September 20, 20269 min readBy ACY Partner Indonesia
TypeScript union and intersection types illustration
300 × 250Ad Space AvailablePlace your ad here

Real programs are full of values that refuse to sit still. An input field might hold a number or be left empty. A function might hand you back a result, or it might hand you back an error. A setting could be the word "auto" or an actual pixel count. In the real world, a single slot often holds more than one kind of thing.

TypeScript has two small but powerful tools for describing exactly this kind of flexibility: union types and intersection types. They sound technical, but the ideas behind them are everyday-simple. One says “this can be either of these.” The other says “this must be all of these at once.” Once these click, a huge amount of TypeScript stops feeling mysterious. Let’s walk through both, slowly.

What a union type actually means

A union type describes a value that can be one of several types. You write it with the pipe symbol |, which you can read out loud as the word “or.”

let id: string | number;

id = "abc-123"; // fine, it's a string
id = 42;        // also fine, it's a number
id = true;      // Error: boolean is not allowed

That single line, string | number, says: “the value in id is allowed to be a string, or a number — and nothing else.” TypeScript will happily accept either one, and it will firmly reject anything outside that list.

Think of it like a parking spot reserved for “cars or motorbikes.” Both are welcome. A truck is not. The sign isn’t vague; it lists exactly what fits.

You are not limited to two options. A union can have as many members as you need:

type Status = "loading" | "success" | "error";

let pageState: Status = "loading";
pageState = "error";   // fine
pageState = "done";    // Error: not one of the allowed values

Notice something interesting here. The members of this union aren’t even types like string or number — they’re specific string values. TypeScript lets you build a type out of exact values, and a union of them gives you a clean, self-documenting list of the only allowed options. This pattern shows up constantly.

Why unions are everywhere

Once you start looking, you’ll see unions all over real code. They tend to appear whenever a value has more than one legitimate shape.

A common one is “a value, or nothing yet”:

type User = {
  name: string;
  email: string;
};

let currentUser: User | null = null;

// later, after someone logs in:
currentUser = { name: "Jane Doe", email: "jane@example.com" };

Here User | null means the variable holds a real user object or the value null (a deliberate “nothing here”). That’s honest. Before login there is no user, so the type says so out loud, and TypeScript will remind you to handle the empty case.

Another classic is the success-or-error result:

type SuccessResult = {
  ok: true;
  data: string;
};

type ErrorResult = {
  ok: false;
  message: string;
};

type FetchResult = SuccessResult | ErrorResult;

A FetchResult is either a success (with data) or an error (with message), never an awkward mix of both. This mirrors how the real operation behaves: it works, or it fails. The type follows reality instead of fighting it.

Read the pipe as 'or'

Whenever you see A | B, mentally say “A or B.” A string | number is “a string or a number.” A User | null is “a user or nothing.” That little habit makes even long unions easy to read at a glance.

The catch: a union only gives you the shared parts

Unions are great, but there’s a rule you have to respect. When a value could be one of several types, TypeScript only lets you use the things that are guaranteed to exist for every member of the union. Anything specific to just one member is off-limits until you prove which one you’re holding.

function printId(id: string | number) {
  // id.toUpperCase(); // Error: numbers don't have toUpperCase
  console.log(id);     // fine — every type can be printed
}

toUpperCase() is a string method. But id might be a number, and numbers don’t have it. So TypeScript blocks the call to keep you safe. This isn’t TypeScript being difficult — it’s protecting you from a crash that would happen at runtime the moment someone passed in a number.

This is the natural tension of unions. They give you flexibility about what a value can be, and in exchange they ask you to be careful about what you do with it.

Narrowing: safely using a union

The way out of that restriction is called narrowing. Narrowing means writing a small check that proves, in a particular branch of code, exactly which member of the union you’re dealing with. Once TypeScript is convinced, it relaxes and lets you use that type’s specific features.

The simplest tool for this is the typeof check:

function printId(id: string | number) {
  if (typeof id === "string") {
    // In here, TypeScript knows id is a string
    console.log(id.toUpperCase());
  } else {
    // And in here, it must be a number
    console.log(id.toFixed(2));
  }
}

Inside the if branch, you’ve proven id is a string, so toUpperCase() is allowed. Inside the else, the only remaining possibility is a number, so toFixed() is allowed. You’ve split one flexible value into two confident branches.

        id: string | number

        typeof id === "string"?
            ┌────┴────┐
          yes         no
           │           │
     id is string  id is number
   .toUpperCase()   .toFixed(2)

There are several ways to narrow — typeof, checking for a specific property, comparing against a known value — and they each deserve a closer look. For now, the key idea to carry forward is this: a union gives you flexibility, and narrowing gives that flexibility back its safety. They are two halves of the same tool.

Narrowing is a whole topic of its own

We’re only touching narrowing lightly here so the union concept makes sense. The different techniques — and the way TypeScript tracks your checks branch by branch — are worth studying on their own once unions feel comfortable.

Intersection types: combining instead of choosing

If a union is “either of these,” an intersection is the opposite idea: “all of these, combined into one.” You write it with the ampersand &, which you can read as the word “and.”

Intersections are most useful with object types. They let you take two separate shapes and merge them into a single shape that has every property from both.

type Person = {
  name: string;
  age: number;
};

type Employee = {
  employeeId: string;
  department: string;
};

type StaffMember = Person & Employee;

A StaffMember is a Person and an Employee at the same time. That means an actual value must satisfy both — it needs every property from each:

const dev: StaffMember = {
  name: "John Doe",
  age: 29,
  employeeId: "ACY-0481",
  department: "Engineering",
};

Leave out any one of those four properties and TypeScript complains, because a StaffMember is the complete combination, not a pick-one. This is handy when you’re building up complex objects from small, reusable pieces. You describe each piece once, then stitch them together with &.

A frequent real pattern is layering extra fields onto a base shape:

type BaseRecord = {
  id: string;
  createdAt: string;
};

type Article = BaseRecord & {
  title: string;
  body: string;
};

Every Article automatically carries id and createdAt from BaseRecord, plus its own title and body. You wrote the common fields once and reused them — no copy-paste.

Union vs intersection, side by side

It’s easy to mix up which symbol does what, so here’s the contrast in one place.

Aspect Union A | B Intersection A & B
Read it as “A or B” “A and B”
A value must be one of the types all of the types at once
Properties you can use only the shared ones (until you narrow) every property from both
Typical use a value with several possible shapes merging object shapes together
Everyday phrase “string or number” “person and employee”

A small mental trick: a union of objects gives you fewer guaranteed properties (only what they share), while an intersection gives you more (everything from each side). That feels backwards at first — “or gives less, and gives more?” — but it makes sense once you remember that a union member only has to satisfy one side, so you can only count on the overlap.

Don't intersect conflicting primitives

Intersection shines with object types. If you try string & number, you get a type that nothing can ever satisfy (a value can’t be a string and a number at the same time). TypeScript may not error immediately, but no real value will fit. Reach for & to combine object shapes, not to mix unrelated primitives.

A quick combined example

These two tools often work together. You can have a union where each member is itself an intersection, or fields that are unions inside an intersected shape. Here’s a compact taste:

type ApiResponse =
  | { status: "ok"; data: string }
  | { status: "fail"; error: string };

function handle(res: ApiResponse) {
  if (res.status === "ok") {
    console.log(res.data);   // narrowed to the success shape
  } else {
    console.log(res.error);  // narrowed to the error shape
  }
}

The status field acts as a label that tells the two union members apart. Checking it narrows the value cleanly into one branch or the other — the same narrowing idea from before, now driving a real decision. This combination of “a union of labelled shapes plus a check on the label” is one of the most useful patterns in all of TypeScript.

Recap

Union and intersection types are how TypeScript describes values that don’t fit a single neat box.

  • A union (A | B) means a value is one of several types — “string or number,” “user or nothing,” “success or error.” Unions show up everywhere because real values often have more than one valid shape.
  • With a union, you can only use the parts shared by every member until you narrow — a small check, like typeof, that proves which type you’re holding so TypeScript lets you use its specific features safely.
  • An intersection (A & B) means a value is all of several types at once, combining their properties. It’s the go-to tool for merging object shapes out of small, reusable pieces.
  • Read | as “or” and & as “and,” remember that a union gives you the shared parts while an intersection gives you the combined parts, and you’ll have the core of TypeScript’s type system in your pocket.

Get comfortable with these two, and the next natural step is narrowing in depth — the set of techniques that turn a flexible union into safe, confident code.

Tags:typescriptunion typesintersection typestype systemfrontend
728 × 90Ad Space AvailablePlace your ad here

Related Articles

See All Articles

You Might Also Like

Frontend best practices overview cover
Frontend / Fundamentals

Frontend Best Practices: An Overview

A friendly map of what good frontend really means — semantic HTML, clean CSS, unobtrusive JavaScript, responsive layouts, performance, and progressive enhancement, with pointers to go deeper.

Sep 20, 20268 min read
A Frontend Developer Roadmap cover with the path HTML to CSS to JavaScript
Frontend / Fundamentals

A Frontend Developer Roadmap: What to Learn, and in What Order

A calm, step-by-step learning path for beginners: HTML, CSS, JavaScript, developer tools, responsive and accessible design, a framework, TypeScript, and deployment, with the reasoning behind each step.

Sep 20, 202611 min read
Title card reading Styling and Interactivity over a dark blue ACY Partner background
Frontend / Fundamentals

How Styling and Interactivity Work

A beginner-friendly look at how CSS turns plain HTML into a designed layout, and how JavaScript adds behavior — the structure, style, behavior mental model for building web pages.

Sep 20, 20269 min read
Three front-end layers combining on one web page
Frontend / Fundamentals

How HTML, CSS, and JavaScript Work Together

A hands-on walkthrough for beginners: build one small button, give it structure with HTML, looks with CSS, and behavior with JavaScript, and watch the three layers cooperate on a real page.

Sep 20, 20268 min read
The Frontend Developer Toolkit cover with editor, browser, and CLI motifs
Frontend / Fundamentals

The Frontend Developer Toolkit

A friendly tour of the everyday tools a frontend developer relies on — code editor, browser and DevTools, terminal, version control, package managers, and a dev server — and what each one is actually for.

Sep 20, 202610 min read
Cover illustration for What Frontend Developers Do
Frontend / Fundamentals

What Frontend Developers Do: A Beginner's Guide to the Role

A clear, beginner-friendly look at what frontend developers actually do every day: turning designs into working interfaces, building reusable UI, and caring about responsiveness, accessibility, and speed.

Sep 20, 202610 min read