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.
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'.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.
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.
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 DirectionIntersection 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.
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:
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 }