Skip to main content

Intersection types in TypeScript

Intersection type - a TypeScript type created with & that requires a value to satisfy every combined type at once.

Theory

TL;DR

  • Think of a Swiss Army knife: every separate gadget's tools packed into one object. You get all capabilities, but the object must carry all of them.
  • & requires ALL properties from every combined type. | requires only one type to match.
  • Nested types intersect deeply. {data: {x: number}} & {data: {y: string}} gives {data: {x: number; y: string}}, not a replacement.
  • Conflicting primitives (string & number) become never. That is a compile error, not a runtime one.
  • Use & to compose object shapes from existing types: props + theme, user + admin, base config + db config.

Quick example

typescript
type Name = { name: string }; type Age = { age: number }; type Person = Name & Age; // must have BOTH name and age const alice: Person = { name: "Alice", age: 25 }; // ✅ works const bob: Person = { name: "Bob" }; // ❌ Error: missing 'age' console.log(alice.name); // "Alice" console.log(alice.age); // 25

Person is not a union of possibilities. It is one strict type that demands every property from Name and Age at the same time.

Key difference from union types

With A | B, a value needs to match only one of the types. With A & B, it must match both simultaneously. So a union might block you from accessing propA without a type guard, while an intersection makes propA and propB always directly accessible.

When to use

  • Role-based types: Admin & User where an admin has extra privileges but also all user fields.
  • Express requests: Request & { user: User } in auth middleware (Passport.js patterns).
  • React component props: ButtonProps & ThemeProps when one component accepts both sets.
  • Avoid when types share a property with incompatible value types. That property becomes never.

Intersection vs Union

AspectA & BA | B
RequirementMust match all typesMust match any one type
Properties you getAll from A and all from BOnly common properties guaranteed
Access safetyobj.propA and obj.propB always safeType guard needed: if ('propA' in obj)
Conflicting propertyBecomes neverNot applicable
Use caseComposing objectsRepresenting alternatives

How the compiler handles this

TypeScript evaluates A & B at compile time only. No runtime cost. The compiler merges required properties from all combined types into the result. Optional properties stay optional. When two types share the same property key with incompatible value types, that property becomes never in the intersection. The compiled JavaScript sees plain objects with no trace of intersection logic.

One thing I have seen trip up developers: TypeScript intersects nested types deeply, not shallowly. { data: { x: number } } & { data: { y: string } } gives { data: { x: number; y: string } }, not a replacement of data. Engineers coming from plain JavaScript often assume B overwrites A here, the way Object.assign would. It does not.

Common mistakes

Assuming nested properties overwrite:

typescript
type A = { data: { x: number } }; type B = { data: { y: string } }; type Merged = A & B; // data: { x: number; y: string } -- NOT just { y: string } const bad: Merged = { data: { x: 1 } }; // ❌ missing 'y' const ok: Merged = { data: { x: 1, y: 'hi' } }; // ✅

If you need override behavior, use Pick<B, 'data'> & Omit<A, 'data'> instead.

Intersecting incompatible primitives:

typescript
type ID = string & number; // never const id: ID = 'abc'; // ❌ type 'never' has no valid assignments

For branded string types, use string & { readonly brand: unique symbol } instead.

Optional vs required conflict:

typescript
type OptA = { prop?: string }; type ReqB = { prop: number }; type Both = OptA & ReqB; // prop becomes required number

The intersection always takes the stricter side. Optional loses to required.

Real-world usage

  • React: ButtonProps & { variant: 'primary' | 'secondary' } in component libraries like Material-UI.
  • Express: Request & { user: User } in Passport.js middleware patterns.
  • Redux Toolkit: PayloadAction<string> & { meta: { timestamp: number } } for typed actions.
  • Zod: z.intersection(schemaA, schemaB) mirrors this behavior at the schema level.

Follow-up questions

Q: What is the difference between type A = B & C and interface A extends B, C?
A: Both produce equivalent types in most cases. interface supports declaration merging, so you can reopen it later to add properties. type is fixed after definition. For object shapes in shared libraries, interface is often more flexible.

Q: What happens when you write string & number?
A: It becomes never. No value can satisfy both primitive types at once, so the type is uninhabitable.

Q: Can you intersect a union with another type?
A: Yes. TypeScript distributes: (A | B) & C becomes (A & C) | (B & C). This is useful when you need to narrow a union.

Q: Does intersection have any runtime cost?
A: None. TypeScript erases all type information during compilation. V8 sees plain JavaScript objects.

Examples

Basic: combining two object types

typescript
type Address = { street: string; city: string; }; type ContactInfo = { email: string; phone: string; }; type UserProfile = Address & ContactInfo; const user: UserProfile = { street: '123 Main St', city: 'Kyiv', email: 'user@example.com', phone: '+380 67 000 0000', }; // all four fields are required

Skip any one field and the compiler errors immediately. No runtime check needed.

Intermediate: React component with combined props

typescript
interface ButtonProps { onClick: () => void; children: React.ReactNode; } interface ThemeProps { variant: 'primary' | 'secondary'; size: 'small' | 'large'; } type ThemedButtonProps = ButtonProps & ThemeProps; const ThemedButton: React.FC<ThemedButtonProps> = ({ onClick, children, variant, size, }) => ( <button className={`${variant} ${size}`} onClick={onClick}> {children} </button> ); // <ThemedButton variant="primary" size="large" onClick={() => {}}>Submit</ThemedButton>

TypeScript errors if variant or onClick is missing. IntelliSense shows all four props in autocomplete. Keeping ButtonProps and ThemeProps separate means each can be reused independently across other components.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?