JS Guide
HomeQuestionsTopicsCompaniesResources
BookmarksSearch

Built for developers preparing for JavaScript, React & TypeScript interviews.

ResourcesQuestionsSupport
HomeQuestionsSearchProgress
HomeTopicstypescriptUnion & Intersection Types
PrevNext
typescript
beginner
8 min read

Union & Intersection Types

discriminated-unions
exhaustive-checking
intersections
narrowing
type-narrowing
unions

Union types (A | B) represent values that can be one of several types and require narrowing to access type-specific properties — intersection types (A & B) combine multiple types into one that has all properties, and discriminated unions with a shared literal field are the most type-safe pattern for handling variants.

Key Points

1Union Types (|)

A value can be one of several types — only shared members are accessible without narrowing. Use typeof, instanceof, or discriminant checks to narrow.

2Discriminated Unions

A shared literal property (kind, type, status) acts as a tag for exhaustive narrowing in switch statements — the standard pattern for variant types.

3Intersection Types (&)

Combines types so the result has all properties from every constituent — used for mixins and composing partial types.

4Intersection Pitfalls

Intersecting incompatible property types silently produces never — interface extends catches conflicts explicitly and is safer for object composition.

What You'll Learn

  • Explain the difference between union and intersection types
  • Implement discriminated unions with exhaustive checking
  • Narrow union types using typeof, instanceof, and discriminant properties
  • Know when intersection can silently produce never and how to avoid it

Deep Dive

Union and intersection types are the two fundamental type composition operators in TypeScript. Unions model "either/or" relationships; intersections model "both/and" relationships.

Union Types (|)

A union type allows a value to be one of several types:

TypeScript
let id: string | number;
id = 'abc'; // OK
id = 123;   // OK
id = true;  // Error: boolean not assignable to string | number

With a union, you can only access members that exist on all variants. id.toString() works (both string and number have it), but id.toUpperCase() errors because number doesn't have that method. To access type-specific members, you need to narrow.

Type Narrowing

Narrowing is the process of refining a union type to a specific member:

TypeScript
function format(id: string | number): string {
  if (typeof id === 'string') {
    return id.toUpperCase(); // id: string
  }
  return id.toFixed(2); // id: number
}

TypeScript narrows automatically through typeof, instanceof, in, equality checks, truthiness, and assignment. After a narrowing check, TypeScript knows the specific type within that branch.

Discriminated Unions

The most powerful union pattern uses a common literal property as a discriminant:

TypeScript
type Circle = { kind: 'circle'; radius: number };
type Rectangle = { kind: 'rectangle'; width: number; height: number };
type Shape = Circle | Rectangle;
 
function area(shape: Shape): number {
  switch (shape.kind) {
    case 'circle': return Math.PI * shape.radius ** 2;
    case 'rectangle': return shape.width * shape.height;
  }
}

The kind property (could be called type, status, or any name) acts as a tag. Checking the discriminant narrows to the specific variant. Adding a default: never case ensures exhaustive handling — if you add a new Shape variant, TypeScript errors on the unhandled case.

Discriminated unions are the standard pattern for Redux actions, API responses, state machines, and any scenario with distinct variants.

Intersection Types (&)

An intersection type combines multiple types — the result has all properties from every constituent:

TypeScript
type HasName = { name: string };
type HasAge = { age: number };
type Person = HasName & HasAge; // { name: string; age: number }

Intersections are commonly used for mixins and composing partial types. Unlike interface extends, intersections work with any type, not just interfaces.

Intersection Pitfalls

Intersecting incompatible types produces never: string & number is never because no value can be both. This happens silently — if two intersected object types have the same property with different types, that property becomes never, and the whole type becomes unusable without clear error messages. Interface extends would catch this conflict explicitly.

Union vs Intersection Intuition

The naming can be confusing:

  • Union of types means the intersection of their properties (only shared members are accessible)
  • Intersection of types means the union of their properties (all members from all types are accessible)

This makes sense when you think about values: a union value could be either type (so you can only use what's common), while an intersection value is both types simultaneously (so you can use everything).

Practical Patterns

  • Optional fields: string | undefined instead of the ? modifier when you need to distinguish "property is missing" from "property is present but undefined"
  • Nullable types: string | null for values that might be absent
  • Function overloads alternative: (input: string | number) => string | number with narrowing inside
  • Mixin composition: BaseProps & RouterProps & AuthProps for composing component types

Key Interview Distinction

Union (A | B) means the value is one of the types — access only shared properties, narrow to access specific ones. Intersection (A & B) means the value has all properties from every type. Discriminated unions use a shared literal property for exhaustive type-safe narrowing — the preferred pattern for variant types. Intersecting incompatible types produces never silently.

Fun Fact

The terms 'union' and 'intersection' in TypeScript come from set theory, but they can feel backwards: a union type gives you access to the intersection of properties, and an intersection type gives you access to the union of properties. This counter-intuitive naming is technically correct (it describes the set of valid values, not the set of accessible properties) but trips up almost every TypeScript learner.

Learn These First

Basic Types

beginner

Continue Learning

Type Guards & Narrowing

intermediate

Interfaces vs Type Aliases

beginner

Practice What You Learned

What are union and intersection types in TypeScript?
junior
unions
Union types (A | B) represent values that can be one of several types - use when a value could be different types. Intersection types (A & B) combine multiple types into one - use when a value must satisfy all types simultaneously.
Previous
Basic Types
Next
Utility Types
PrevNext