1. Introduction
Union types and intersection types let you combine existing types into new ones. A union type (A | B) describes a value that could be either type A or type B. An intersection type (A & B) describes a value that must satisfy both type A and type B simultaneously. Together they are two of the most-used tools for modeling real-world data shapes in TypeScript, from API responses to component props.
Cricket analogy: Think of a batting slot filled by either Rohit Sharma or Shubman Gill (a union) versus an all-rounder like Ravindra Jadeja who must field, bowl, and bat (an intersection) — API-response shapes work the same way.
Unions are common when a value can legitimately take multiple forms -- a function that accepts a string or a number, or a variable representing one of several states. Intersections are common when you want to merge multiple object shapes together, such as combining a base set of properties with role-specific properties.
Cricket analogy: A toss result that could be 'bat' or 'bowl' is a classic union of possible states, while a captain's profile merging base player stats with captaincy-specific fields like toss-win record is an intersection.
2. Syntax
// Union type: value is one of the listed types
let id: string | number;
id = "abc123";
id = 42;
// Intersection type: value must satisfy all listed types
interface Named { name: string; }
interface Aged { age: number; }
type Person = Named & Aged;
const p: Person = { name: "Sam", age: 30 };3. Explanation
With a union type A | B, TypeScript only lets you directly access members that exist on BOTH A and B without first narrowing the type. To use a member that exists only on one branch of the union, you must first perform type narrowing -- using a type guard such as typeof, instanceof, an in check, a discriminant property comparison, or a user-defined type predicate (value is Foo). Once narrowed inside a conditional branch, TypeScript knows exactly which member of the union you are working with and allows access to type-specific properties.
Cricket analogy: With a Rohit Sharma-or-Jasprit Bumrah union you can only reference shared stats like matches played until you narrow with an 'in' check for 'bowlingAverage' to safely access Bumrah's bowler-only figures.
Intersection types work the opposite way for objects: A & B combines ALL properties from both A and B into a single type that must have everything. This is useful for mixins and composing smaller interfaces into a richer one. However, if you intersect incompatible primitive types -- for example string & number -- there is no value that can simultaneously be both a string and a number, so TypeScript infers the result as the never type, since no value can satisfy that constraint.
Cricket analogy: An all-rounder type formed by intersecting Batter & Bowler must carry every property from both, such as strikeRate and economyRate, but intersecting Batter & Umpire, roles that can't coexist in one match, collapses to 'never'.
Gotcha: Accessing a union type's member that isn't shared by all members without narrowing first raises a compile error ("Property does not exist on type ..."). Also, intersecting incompatible primitive types like string & number silently collapses to never -- a common source of confusing errors when combining generic type parameters.
4. Example
type Cat = { kind: "cat"; meow(): string };
type Dog = { kind: "dog"; bark(): string };
type Pet = Cat | Dog;
function speak(pet: Pet): string {
// Narrowing with a discriminant property before accessing kind-specific methods
if (pet.kind === "cat") {
return pet.meow();
} else {
return pet.bark();
}
}
const cat: Cat = { kind: "cat", meow: () => "Meow!" };
const dog: Dog = { kind: "dog", bark: () => "Woof!" };
console.log(speak(cat));
console.log(speak(dog));
// Intersection combining two shapes
interface HasId { id: number; }
interface HasName { name: string; }
type Entity = HasId & HasName;
const entity: Entity = { id: 1, name: "Widget" };
console.log(entity);5. Output
Meow!
Woof!
{ id: 1, name: 'Widget' }6. Key Takeaways
Practice what you learned
1. Given `let value: string | number;`, why can't you call `value.toUpperCase()` directly?
2. What is the result type of `type X = string & number;`?
3. Which of the following is a valid way to narrow a union type inside an `if` block?
4. What does an intersection type `A & B` produce for two object interfaces?
5. In a discriminated union like `{ kind: "cat"; ... } | { kind: "dog"; ... }`, what enables safe narrowing?
Was this page helpful?
You May Also Like
Type Guards in TypeScript
Narrow union types safely within conditional blocks using `typeof`, `instanceof`, and custom user-defined type predicates with `is`.
Discriminated Unions in TypeScript
Use a shared literal 'tag' property across union members so TypeScript can narrow exactly which member you're handling in a switch or if.
Type Aliases in TypeScript
Use `type` to give a name to any type -- primitives, unions, tuples, function signatures, and more.
Interfaces in TypeScript
Learn how TypeScript interfaces describe the shape of objects using structural typing, and why they are the primary tool for contract-based design.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics