An intersection type, written A & B, describes a value that is an A and a B at the same time. For object types that means the value has every property of both. It is how you combine existing types without writing the properties out again.
The second object leaves out team, so it is compile error TS2322, and the next line of the message says Property 'team' is missing ... but required in type 'Employee'. A Staff value can be passed anywhere a Person or an Employee is expected.
Combining Object Types
& works with any mix of type aliases, interfaces and inline object types, and with generic type parameters. That last case is where it is hard to replace: a function that adds properties to whatever object it receives can say so precisely.
The caller keeps the exact type of what it passed in (title, words) plus the two added properties. An interface ... extends cannot express "whatever T is, plus these", because an interface cannot extend a type parameter.
Conflicting Properties Become never
When both sides declare the same property, the property's type is the intersection of the two. If those types have no value in common, the property becomes never, and the compiler says nothing until you try to create a value:
index.ts(7,22): error TS2322: Type 'string' is not assignable to type 'never'.
The error points at the object, not at the type that caused it, which makes these bugs slow to trace. Hovering r.id in the editor shows its type: never. When the conflicting property is a literal tag, as in type Shape = { kind: "circle" } & { kind: "square" }, TypeScript goes further and reduces the whole intersection to never. Reading a property of such a value then explains why: Property 'kind' does not exist on type 'never'. The intersection 'Shape' was reduced to 'never' because property 'kind' has conflicting types in some constituents.
interface ... extends catches the same conflict at the declaration instead, with error TS2430. That is the main practical difference between the two; the interface vs type page compares them side by side.
Compatible Overlaps Narrow the Property
If the two property types do overlap, the result is the overlap. That is useful, not an error:
The second half shows what & does to unions: it keeps the members both sides have in common. Thinking of types as sets of values makes this predictable. A | B is the union of the two sets, A & B is their overlap, and an overlap with nothing in it is never.
Intersection vs Union
The names come from set theory, and they feel backwards when applied to object properties:
A | B (union) | A & B (intersection) | |
|---|---|---|
| A value is | an A or a B | an A and a B |
| Set of allowed values | larger | smaller |
| Properties you can use | only those in both | all from either |
string with number | string | number | never |
"a" | "b" with "b" | "c" | "a" | "b" | "c" | "b" |
An intersection of object types has more properties precisely because it allows fewer values: only objects that have everything.
Intersection vs extends
type C = A & B | interface C extends A, B | |
|---|---|---|
| Works with | any types, including unions and type parameters | object types with statically known members |
| Conflicting property | silently becomes never | error TS2430 or TS2320 at the declaration |
| Result | an intersection, checked constituent by constituent | one flat named type whose relationships are cached |
| Large compositions | can slow type checking | preferred by the Performance page of the TypeScript wiki |
For combining a few object type aliases, & is idiomatic and fine. For a type built from many parts, or a public API type, extends gives earlier errors and cheaper type checks.
Intersections with Primitives: Branding
Intersecting a primitive with an object type does not produce never: string & { readonly __brand: "UserId" } is a string that carries an extra, compile-time-only marker. No real string has that property, which is exactly the point: only code that deliberately asserts the brand can create one, so a plain string or an OrderId can no longer be passed where a UserId is expected. That technique has its own page, branded types.
Frequently Asked Questions
What is an intersection type in TypeScript?
A type written A & B whose values must satisfy both A and B at the same time. For object types, that means the value has every property of A and every property of B. It is the usual way to combine two type aliases into one.
What is the difference between a union and an intersection?
A union A | B means "either one": the value can be an A or a B, and you can only use what they share until you narrow. An intersection A & B means "both": the value has everything from both. With object types, the union accepts more values and the intersection has more properties.
Why is my intersection type never?
Because no value can satisfy both sides. string & number is never, and { id: string } & { id: number } makes id a string & number, so the property is never and no object can be created. If two object types have the same literal tag with different values (kind: "circle" and kind: "square"), the whole intersection reduces to never.
Should I use an intersection or extends?
Both combine object types. interface X extends A, B reports conflicting properties at the declaration and is recommended by the TypeScript team for composing large object types. & works with any type, including unions and generic parameters, which extends cannot combine. Use & for type aliases and generic helpers, and extends when building interfaces.
How do I merge two object types in TypeScript?
Write type Merged = A & B. For the runtime value, spread both objects: const merged: A & B = { ...a, ...b }. If A and B share a property with different types, the type becomes never for that property; use Omit<A, keyof B> & B when the second object's properties should replace the first's.