← thecodex.expert · The Codex Family of Knowledge
TypeScript

Union and Intersection Types

string | number means exactly what it looks like — this value is a string OR a number, and TypeScript will not let you treat it as either one until you’ve checked which.

TypeScript 6.0 | for OR, & for AND Last verified:
Where this bites you

An intersection keeps every property from both sides — including two properties with the same name and incompatible types. { id: string } & { id: number } doesn't pick one; it asks for an id that is a string and a number at the same time, which is never. TypeScript accepts the type definition silently, and the error only shows up later, at the first place you try to build a value of it, with a message that never mentions the intersection.

TypeScriptnever_bug.ts
type ApiRecord = { id: string; createdAt: string };
type DbRow = { id: number; version: number };

type Synced = ApiRecord & DbRow;      // no error here

const row: Synced = { id: "a1", createdAt: "2026-10-12", version: 3 };
// error TS2322: Type 'string' is not assignable to type 'never'.
Canonical Definition

A union type, written with |, describes a value that could be any one of several types — TypeScript only allows operations valid on every member of the union unless the type has been narrowed first. An intersection type, written with &, combines multiple types into one that has all of their members simultaneously, commonly used to merge two separate object type shapes into a single richer one.

Union types: one of several possibilities

Without narrowing, TypeScript only permits operations valid on EVERY member of the union — calling .toUpperCase() directly would be a compile error, since a number doesn't have that method.

TypeScriptunion_types.ts
function printId(id: string | number) {
    // id.toUpperCase();   // COMPILE ERROR: number doesn't have toUpperCase
    if (typeof id === "string") {
        console.log(id.toUpperCase());   // safe: TypeScript knows id is a string here
    } else {
        console.log(id);
    }
}

Union of literal types

A union isn't limited to whole types like string or number — unioning specific literal values creates a precise, closed set of allowed values, similar in spirit to an enum but often more idiomatic in TypeScript.

TypeScriptliteral_union.ts
type Direction = "north" | "south" | "east" | "west";

function move(direction: Direction) {
    console.log(`Moving ${direction}`);
}

move("north");    // fine
// move("up");     // COMPILE ERROR: "up" is not assignable to type Direction

Intersection types: combining shapes into one

The resulting type must satisfy both original types at once — an intersection of two object types has every property from both.

TypeScriptintersection_types.ts
type HasName = { name: string };
type HasAge = { age: number };

type Person = HasName & HasAge;   // must have BOTH name and age

const p: Person = { name: "Alice", age: 30 };   // must satisfy both types
// const bad: Person = { name: "Bob" };          // COMPILE ERROR: missing 'age'

Intersections merge shapes; they don't resolve conflicts. When two types share a property name with different meanings, decide which one wins and rename the other before intersecting — Omit removes the conflicting key from one side:

TypeScriptnever_fix.ts
type Synced = Omit<ApiRecord, "id"> & DbRow & { apiId: string };

const row: Synced = { id: 7, apiId: "a1", createdAt: "2026-10-12", version: 3 };
console.log(row);   // { id: 7, apiId: 'a1', createdAt: '2026-10-12', version: 3 }

Sources

1
Microsoft. TypeScript Handbook, "Union Types" and "Intersection Types", typescriptlang.org/docs/handbook/2/everyday-types.html.