Skip to main content

Intersections

Sometimes it is useful to create a type which is all of a set of other types. For example, you might want to write a function which accepts a value that implements two different interfaces:

1interface Serializable {2  serialize(): string;3}4
5interface HasLength {6  length: number;7}8
9function func(value: Serializable & HasLength) {10  // ...11}12
13func({14  length: 3,15  serialize() {16    return '3';17  },18}); // Works19
20func({length: 3}); // Error! Doesn't implement both interfaces
20:6-20:16: Cannot call `func` with object literal bound to `value` because property `serialize` is missing in object literal [1] but exists in `Serializable` [2]. [prop-missing]

Intersection type syntax

Intersection types are any number of types which are joined by an ampersand &.

Type1 & Type2 & ... & TypeN

You may also add a leading ampersand which is useful when breaking intersection types onto multiple lines.

type Foo =
& Type1
& Type2
& ...
& TypeN

Each of the members of a intersection type can be any type, even another intersection type.

type Foo = Type1 & Type2;
type Bar = Type3 & Type4;

type Baz = Foo & Bar;

Intersection types require all in, but one out

Intersection types are the opposite of union types. When calling a function that accepts an intersection type, we must pass in all of those types. But inside of our function we only have to treat it as any one of those types.

1type A = {a: number, ...};2type B = {b: boolean, ...};3type C = {c: string, ...};4
5function func(value: A & B & C) {6  const a: A = value;7  const b: B = value;8  const c: C = value;9}

Even as we treat our value as just one of the types, we do not get an error because it satisfies all of them.

Intersection of function types

A common use of intersection types is to express functions that return different results based on the input we pass in. Suppose for example that we want to write the type of a function that:

  • returns a string, when we pass in the value "string",
  • returns a number, when we pass in the value "number", and
  • returns any possible type (mixed), when we pass in any other string.

The type of this function will be

1type Fn =2  & ((x: "string") => string)3  & ((x: "number") => number)4  & ((x: string) => mixed);

Each line in the above definition is called an overload, and we say that functions of type Fn are overloaded.

Note the use of parentheses around the arrow types. These are necessary to override the precedence of the "arrow" constructor over the intersection.

Calling an overloaded function

Using the above definition we can declare a function fn that has the following behavior:

1declare const fn:2  & ((x: "string") => string)3  & ((x: "number") => number)4  & ((x: string) => mixed);5
6const s: string = fn("string"); // Works7const n: number = fn("number"); // Works8const b: boolean = fn("boolean"); // Error!
8:20-8:32: Cannot assign `fn(...)` to `b` because mixed [1] is incompatible with boolean [2]. [incompatible-type]

Flow achieves this behavior by matching the type of the argument to the first overload with a compatible parameter type. Notice for example that the argument "string" matches both the first and the last overload. Flow will just pick the first one. If no overload matches, Flow will raise an error at the call site.

Declaring overloaded functions

An equivalent way to declare the same function fn would be by using consecutive "declare function" statements

1declare function fn(x: "string"): string;2declare function fn(x: "number"): number;3declare function fn(x: string): mixed;

A limitation in Flow is that it can't check the body of a function against an intersection type. In other words, if we provided the following implementation for fn right after the above declarations

1function fn(x: mixed) {2  if (x === "string") { return ""; }3  else if (x === "number") { return 0; }4  else { return null; }5}

Flow silently accepts it (and uses Fn as the inferred type), but does not check the implementation against this signature. This makes this kind of declaration a better suited candidate for library definitions, where implementations are omitted.

Intersections of object types

When you create an intersection of inexact object types, you are saying that your object satisfies each member of the intersection.

For example, when you create an intersection of two inexact objects with different sets of properties, it will result in an object with all of the properties.

1type One = {foo: number, ...};2type Two = {bar: boolean, ...};3
4type Both = One & Two;5
6const value: Both = {7  foo: 1,8  bar: true9};

When you have properties that overlap by having the same name, Flow follows the same strategy as with overloaded functions: it will return the type of the first property that matches this name.

For example, if you merge two inexact objects with a property named prop, first with a type of number and second with a type of boolean, accessing prop will return number.

1type One = {prop: number, ...};2type Two = {prop: boolean, ...};3
4declare const both: One & Two;5
6const prop1: number = both.prop; // Works7const prop2: boolean = both.prop; // Error!
7:24-7:32: Cannot assign `both.prop` to `prop2` because number [1] is incompatible with boolean [2]. [incompatible-type]

To combine exact object types, you should use object type spread instead:

1type One = {foo: number};2type Two = {bar: boolean};3
4type Both = {5  ...One,6  ...Two,7};8
9const value: Both = {10  foo: 1,11  bar: true12};

Note: When it comes to objects, the order-specific way in which intersection types are implemented in Flow, may often seem counter-intuitive from a set theoretic point of view. In sets, the operands of intersection can change order arbitrarily (commutative property). For this reason, it is a better practice to define this kind of operation over object types using object type spread where the ordering semantics are better specified.

Impossible intersection types

Using intersection types, it is possible to create types which are impossible to create at runtime. Intersection types will allow you to combine any set of types, even ones that conflict with one another.

For example, you can create an intersection of a number and a string.

1type NumberAndString = number & string;2
3function func(value: NumberAndString) { /* ... */ }4
5func(3.14); // Error!
6func('hi'); // Error!
5:6-5:9: Cannot call `func` with `3.14` bound to `value` because number [1] is incompatible with string [2]. [incompatible-call]
6:6-6:9: Cannot call `func` with `'hi'` bound to `value` because string [1] is incompatible with number [2]. [incompatible-call]

But you can't possibly create a value which is both a number and a string, but you can create a type for it. There's no practical use for creating types like this, but it's a side effect of how intersection types work.

An accidental way to create an impossible type is to create an intersection of exact object types. For example:

1function func(obj: {a: number} & {b: string}) { /* ... */ }2
3func({a: 1}); // Error!
4func({b: 'hi'}); // Error!
5func({a: 1, b: 'hi'}); // Error!
3:6-3:11: Cannot call `func` with object literal bound to `obj` because property `a` is missing in object type [1] but exists in object literal [2]. [prop-missing]
3:6-3:11: Cannot call `func` with object literal bound to `obj` because property `b` is missing in object literal [1] but exists in object type [2]. [prop-missing]
4:6-4:14: Cannot call `func` with object literal bound to `obj` because property `a` is missing in object literal [1] but exists in object type [2]. [prop-missing]
4:6-4:14: Cannot call `func` with object literal bound to `obj` because property `b` is missing in object type [1] but exists in object literal [2]. [prop-missing]
5:6-5:20: Cannot call `func` with object literal bound to `obj` because property `a` is missing in object type [1] but exists in object literal [2]. [prop-missing]
5:6-5:20: Cannot call `func` with object literal bound to `obj` because property `b` is missing in object type [1] but exists in object literal [2]. [prop-missing]

It's not possible for an object to have exactly the property a and no other properties, and simultaneously exactly the property b and no other properties.