---
title: "06 — Combining Types: Unions, Intersections, and Aliases"
uid: combining-types
tags: ["union-types", "intersection-types", "type-aliases", "typescript", "roadmap:typescript", "keyof"]
excerpt: "Unions say 'or' (one of several), intersections say 'and' (all combined), aliases name the result. The or/and distinction makes type definitions stop feeling mysterious."
date: 2026-08-13T03:27:28+0000
source: https://www.aveshina.my.id/en/blog/combining-types
---

"Just some operators between type names" was my combining-types model, and it blurred the one distinction that matters. Writing it down separated the two ideas that everything else hangs off: **a union means "or" (a value is one of several types), an intersection means "and" (a value must satisfy all of them at once), and a type alias is how I give the combined result a reusable name.** [1] Once the or/and distinction was sharp, most of the type definitions I write stopped feeling mysterious.

The motivation is that real data doesn't fit neatly into one type. A function might accept a string or a number. An object might need to be both a User and a Timestamped record. The primitive types describe single shapes; combining types is how I describe the shapes that actually occur.

## Union types: "or"

A **union type** declares that a value can be one of several types, separated by the pipe (|) [1]:

```
function format(value: string | number) {
  return value.toString();
}
```

value is either a string or a number, and the compiler allows either. The cost is that inside the function I can only safely use operations common to *all* members of the union until I narrow it down — .toString() works on both, so it's fine, but .toUpperCase() would be an error because numbers don't have it. (Narrowing — how the compiler refines a union inside conditional blocks — gets its own notes.) Unions are the workhorse of flexible APIs: optional values (string | null), polymorphic parameters, and sets of allowed literal values ("GET" | "POST" | "PUT").

## Intersection types: "and"

An **intersection type** combines multiple types into one that has *all* the properties of each, joined with & [2]:

```
type TimestampedUser = User & { createdAt: Date };
```

A TimestampedUser must have everything a User has *and* a createdAt field. The result is the union of all properties — an "and" in the sense of "this and that, together." Intersections shine when composing capabilities: combining a base type with mixins, stacking concerns like Serializable & Cloneable, or extending an imported type without modifying it. The mental image is overlapping sets, where the intersection is the region that satisfies every member.

```figure
<svg viewBox="0 0 720 240" xmlns="http://www.w3.org/2000/svg" class="my-6 w-full max-w-2xl" role="img" aria-label="Union vs intersection. Left: two circles A and B with both fully shaded, labelled union = A OR B. Right: two circles with only the overlapping middle shaded, labelled intersection = A AND B.">
  <g font-family="ui-sans-serif, system-ui, sans-serif" text-rendering="geometricPrecision">

    <!-- UNION -->
    <text x="180" y="36" font-size="14" font-weight="700" fill="#1e1b4b" text-anchor="middle">Union — A | B (or)</text>
    <circle cx="130" cy="130" r="56" fill="#c7d2fe" fill-opacity="0.85" stroke="#6366f1" stroke-width="1.5"/>
    <circle cx="230" cy="130" r="56" fill="#c7d2fe" fill-opacity="0.85" stroke="#6366f1" stroke-width="1.5"/>
    <text x="105" y="135" font-size="13" font-weight="700" fill="#1e1b4b" text-anchor="middle">A</text>
    <text x="255" y="135" font-size="13" font-weight="700" fill="#1e1b4b" text-anchor="middle">B</text>
    <text x="180" y="212" font-size="11" fill="#64748b" text-anchor="middle" font-style="italic">value is one of these types</text>

    <!-- INTERSECTION -->
    <text x="540" y="36" font-size="14" font-weight="700" fill="#052e16" text-anchor="middle">Intersection — A &amp; B (and)</text>
    <circle cx="490" cy="130" r="56" fill="#bbf7d0" fill-opacity="0.45" stroke="#16a34a" stroke-width="1.5"/>
    <circle cx="590" cy="130" r="56" fill="#bbf7d0" fill-opacity="0.45" stroke="#16a34a" stroke-width="1.5"/>
    <path d="M540,130 a56,56 0 0 0 96,0 a56,56 0 0 0 -96,0 z" fill="#16a34a" fill-opacity="0.65"/>
    <text x="465" y="135" font-size="13" font-weight="700" fill="#052e16" text-anchor="middle">A</text>
    <text x="615" y="135" font-size="13" font-weight="700" fill="#052e16" text-anchor="middle">B</text>
    <text x="540" y="212" font-size="11" fill="#64748b" text-anchor="middle" font-style="italic">value satisfies both at once</text>
  </g>
</svg>
```

## Type aliases: naming the result

A **type alias** creates a name for any type — primitive, union, intersection, or object shape [3]:

```
type ID = string | number;
type Point = { x: number; y: number };
```

The alias doesn't create a new type; it's a new *name* for an existing one, used for readability and reuse. This matters most for complex unions or intersections that would be painful to spell out at every use site. Naming a union once and reusing the alias keeps signatures short and gives the concept a vocabulary — ID reads better than string | number scattered through a codebase.

## The keyof operator: keys as a union

The **keyof operator** takes an object type and produces a union of its keys [4]:

```
type User = { id: number; name: string };
type UserKey = keyof User; // "id" | "name"
```

keyof is the bridge between "an object type" and "the set of its property names." It's the foundation for type-safe property access — a function that takes a key and a value can enforce that the key actually exists on the object, and that the value matches that property's type. Almost every generic utility type (and most of the built-in ones) builds on keyof. It's small, but it's the operator that unlocked type-safe metaprogramming for me.

## How I use this

My defaults: reach for a **union** whenever a value can legitimately be one of several shapes — optional fields, polymorphic inputs, sets of allowed strings; reach for an **intersection** when I'm composing capabilities or extending a type I don't own; and reach for a **type alias** the moment a union or intersection appears more than once, so it gets a name. The discipline that pays off is naming concepts — Status, ID, Timestamped<T> — because named combinations document intent in a way inline syntax can't. And whenever I see string | number or similar scattered across signatures, that's the signal to lift it into an alias.

## References

[1] Microsoft, "Union Types," TypeScript Handbook, 2024. [Online]. Available: [https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types)

[2] TypeScript Tutorial, "Intersection Types in TypeScript," 2024. [Online]. Available: [https://www.typescripttutorial.net/typescript-tutorial/typescript-intersection-types/](https://www.typescripttutorial.net/typescript-tutorial/typescript-intersection-types/)

[3] Microsoft, "Type Aliases," TypeScript Handbook, 2024. [Online]. Available: [https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#type-aliases](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#type-aliases)

[4] Microsoft, "Keyof Type Operator," TypeScript Handbook, 2024. [Online]. Available: [https://www.typescriptlang.org/docs/handbook/2/keyof-types.html#handbook-content](https://www.typescriptlang.org/docs/handbook/2/keyof-types.html#handbook-content)

```quiz
Q: A parameter typed `string | number` means the value…
- must be both a string and a number at once
- is one of: a string, or a number
correct: 1
explain: A union (|) means "or." The value is one of the listed types. Inside the function you can only use operations common to all members until you narrow.

Q: `type TimestampedUser = User & { createdAt: Date }` produces a type that…
- has all of User's properties AND createdAt
- has only createdAt
correct: 0
explain: An intersection (&) means "and." The result combines every property of all members, so TimestampedUser has User's fields plus createdAt.

Q: What does `type K = keyof User` produce when User has `id` and `name`?
- the union "id" | "name"
- the type of the values of those properties
correct: 0
explain: keyof extracts the property names as a union of string literal types — here, "id" | "name". It's the bridge from an object type to its set of keys.

Q: A type alias does what?
- Creates a new type distinct from the original
- Creates a new name for an existing type, for reuse and readability
correct: 1
explain: Aliases don't create new types — they name an existing one. They're used to give complex unions, intersections, or object shapes a reusable, readable label.

Q: You keep writing `"GET" | "POST" | "PUT" | "DELETE"` across many signatures. The idiomatic fix is…
- make each one an enum
- create a type alias like `type Method = "GET" | "POST" | "PUT" | "DELETE"` and reuse it
correct: 1
explain: A repeated union is the canonical signal to lift it into a named alias. The alias documents intent and keeps every site in sync.
```
