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) becomenever. 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
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); // 25Person 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 & Userwhere 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 & ThemePropswhen one component accepts both sets. - Avoid when types share a property with incompatible value types. That property becomes
never.
Intersection vs Union
| Aspect | A & B | A | B |
|---|---|---|
| Requirement | Must match all types | Must match any one type |
| Properties you get | All from A and all from B | Only common properties guaranteed |
| Access safety | obj.propA and obj.propB always safe | Type guard needed: if ('propA' in obj) |
| Conflicting property | Becomes never | Not applicable |
| Use case | Composing objects | Representing 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:
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:
type ID = string & number; // never
const id: ID = 'abc'; // ❌ type 'never' has no valid assignmentsFor branded string types, use string & { readonly brand: unique symbol } instead.
Optional vs required conflict:
type OptA = { prop?: string };
type ReqB = { prop: number };
type Both = OptA & ReqB; // prop becomes required numberThe 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
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 requiredSkip any one field and the compiler errors immediately. No runtime check needed.
Intermediate: React component with combined props
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 readyA concise answer to help you respond confidently on this topic during an interview.