Engineering Notes
Home/Languages/TypeScript/Intersection Types
Languages · TypeScript

Intersection Types

An intersection type (&) combines multiple types into one that must satisfy all of them simultaneously.

💻 Languages● Beginner⏱ 5 min read🛠️ 2 exercises🎤 5 interview Q&As

🎯 What you'll learn

  • What an Intersection Type Is
  • The Mixin Pattern
  • Intersecting Function Types — Overload-Like Behavior
  • Intersecting Incompatible Primitives Produces never
  • A Common Trap: Same Property Name, Conflicting Types

1What an Intersection Type Is

An intersection type (&) combines multiple types into one that must satisfy all of them simultaneously.

ts
type Named = { name: string };
type Aged = { age: number };

type Person = Named & Aged;

const p: Person = { name: "Ana", age: 30 }; // must have BOTH name and age
Named and Aged, with no shared property names, merge cleanly into Person requiring both name and age
Named and Aged, with no shared property names, merge cleanly into Person requiring both name and age

Compare with a union (|, its own topic): a union means "could be any one of these"; an intersection means "must be every one of these at once."

2The Mixin Pattern

Intersections are the standard way to express "an object that combines the capabilities of several independent pieces" — commonly used for mixin-style composition.

ts
type Timestamped = { createdAt: Date };
type Taggable = { tags: string[] };
type Identifiable = { id: number };

type Post = Identifiable & Timestamped & Taggable & {
  title: string;
  body: string;
};

const post: Post = {
  id: 1,
  createdAt: new Date(),
  tags: ["typescript"],
  title: "Hello",
  body: "World",
};

3Intersecting Function Types — Overload-Like Behavior

ts
type StringFormatter = (value: string) => string;
type NumberFormatter = (value: number) => string;

type Formatter = StringFormatter & NumberFormatter;

const format: Formatter = (value: string | number): string => {
  return typeof value === "string" ? value.toUpperCase() : value.toFixed(2);
};

A value assigned to an intersection of function types must be callable in a way compatible with every member signature — in practice this behaves similarly to (but is not identical to) function overloads.

4Intersecting Incompatible Primitives Produces never

ts
type Impossible = string & number; // collapses to `never` — no value can be both at once

If two intersected types have genuinely incompatible requirements for the same slot (like being both string and number simultaneously), the result is never — the type that has no possible values at all (full depth in the Any/Unknown/Never topic).

5A Common Trap: Same Property Name, Conflicting Types

ts
type A = { value: string };
type B = { value: number };

type Combined = A & B; // value: string & number → collapses to `value: never`

// const c: Combined = { value: "hi" }; // ❌ impossible — never accepts no values

Even though A and B are otherwise perfectly normal object types, intersecting them when they share a property name with incompatible types silently produces an unusable never for that property — not a compile error on the type Combined = A & B line itself, but on any attempt to actually construct a value of that type.

Under the HoodHow Intersection Is Actually Computed▾

Object-type intersections merge member lists, per-property

For object-like types, the checker computes an intersection by walking each source type's member list and merging them into one combined member list, property by property. When both sides define the same property name, the checker doesn't pick one arbitrarily — it computes the intersection of that property's own type too, recursively applying the same rule.

text
A = { value: string }        B = { value: number }
                 ↓ merge property "value"
       Combined.value : string & number  →  never (section 4's rule, applied per-property)

This is exactly why section 5's trap happens: the collapse to never isn't a special-cased error condition — it falls straight out of the same general merging algorithm used for every intersection, applied recursively down to the conflicting property.

Why intersections of DIFFERENT, non-overlapping shapes just work

ts
type Named = { name: string };
type Aged = { age: number };
type Person = Named & Aged; // no shared property names at all — clean merge, no conflicts

Since Named and Aged never define the same property name, the per-property merge in every case has only one candidate type to use — no intersection-of-property-types step is ever triggered, and the result is simply the union of both member lists, which is exactly the mixin pattern from section 2.

Function type intersections use a different rule: overload-like stacking

Object property merging doesn't apply to call signatures the same way — intersecting function types instead makes the resulting type callable via any of the intersected signatures (closer to how overloads work), rather than trying to merge parameter/return types property-by-property the way object shapes do. This is why section 3's example can be called with either a string or a number argument — the checker treats each original signature as still independently valid on the intersected type.

⚠️ Common Pitfalls

  1. Intersecting two object types that share a property name with different, incompatible types — silently produces an unconstructable never for that property; the error only surfaces later, when you try to actually build a value.
  2. Confusing & (intersection, "must be all of these") with | (union, "could be any of these") — an easy symbol mix-up with very different meaning.
  3. Expecting intersected function types to behave exactly like formal overloads — close, but not identical; genuinely complex overload requirements are better expressed with real overload signatures (a function/method with multiple declared signatures), not an intersection of function types.
  4. Assuming intersection order matters for the result — for object shapes, A & B and B & A produce the same combined shape; order doesn't affect the final merged member set.

📋 Quick Reference

text
A & B                          → must satisfy BOTH — combines requirements
{x: string} & {x: number}       → x collapses to `never`
object shapes, no name clash     → clean merge, mixin pattern
function types                    → callable via either original signature

🎤 Interview Q&A

Questions interviewers ask about this topic. Answer out loud first, then open a card to compare.

Q1What is an intersection type?core▾

A & B combines types: a value must satisfy both, so it has all properties of A and B. Used to compose types and mixins.

Follow-ups interviewers ask
  • Union vs intersection? Union: either; intersection: both.
Q2What happens when intersected properties conflict?core▾

Conflicting property types intersect too: { id: string } & { id: number } makes id: never, so no value can satisfy it. Interfaces with extends report such conflicts as errors earlier.

Follow-ups interviewers ask
  • Why prefer extends sometimes? Clearer errors and better performance.
Q3Where are intersections useful?core▾

Adding properties to existing types (User & { permissions: string[] }), branded types, combining props in React components, and mixin patterns.

Follow-ups interviewers ask
  • React example? type Props = ButtonProps & { loading?: boolean }.
Q4How do intersections interact with unions?senior▾

Intersection distributes over unions: (A | B) & C becomes (A & C) | (B & C), useful for adding common fields to each variant of a discriminated union.

Follow-ups interviewers ask
  • Simplifying display? type Simplify<T> = { [K in keyof T]: T[K] } & {}.
Q5Intersection of functions?core▾

Behaves like overloads: an intersection of function types can be called with either signature.

Follow-ups interviewers ask
  • Common use? Rare; usually explicit overloads are clearer.

🛠️ Practice exercises

Each exercise is a file in this topic folder. Code exercises are self-checking: run them and make the checks pass.