Skip to main content

Interfaces

Classes in Flow are nominally typed. This means that when you have two separate classes you cannot use one in place of the other even when they have the same exact properties and methods:

1class Foo {2  serialize(): string { return '[Foo]'; }3}4
5class Bar {6  serialize(): string { return '[Bar]'; }7}8
9const foo: Foo = new Bar(); // Error!
9:18-9:26: Cannot assign `new Bar()` to `foo` because `Bar` [1] is incompatible with `Foo` [2]. [incompatible-type]

Instead, you can use interface in order to declare the structure of the class that you are expecting.

1interface Serializable {2  serialize(): string;3}4
5class Foo {6  serialize(): string { return '[Foo]'; }7}8
9class Bar {10  serialize(): string { return '[Bar]'; }11}12
13const foo: Serializable = new Foo(); // Works!14const bar: Serializable = new Bar(); // Works!

You can also declare an anonymous interface:

1class Foo {2  a: number;3}4
5function getNumber(o: interface {a: number}): number {6  return o.a;7}8
9getNumber(new Foo()); // Works!

You can also use implements to tell Flow that you want the class to match an interface. This prevents you from making incompatible changes when editing the class.

1interface Serializable {2  serialize(): string;3}4
5class Foo implements Serializable {6  serialize(): string { return '[Foo]'; } // Works!7}8
9class Bar implements Serializable {10  serialize(): number { return 42; } // Error!
11}
10:16-10:21: Cannot implement `Serializable` [1] with `Bar` because number [2] is incompatible with string [3] in the return value of property `serialize`. [incompatible-type]

You can also use implements with multiple interfaces.

class Foo implements Bar, Baz {
// ...
}

Interfaces can describe both instances and objects, unlike object types which can only describe objects:

1class Foo {2  a: number;3}4const foo = new Foo();5const o: {a: number} = {a: 1};6
7interface MyInterface {8  a: number;9}10
11function acceptsMyInterface(x: MyInterface) { /* ... */ }12acceptsMyInterface(o); // Works!13acceptsMyInterface(foo); // Works!14
15function acceptsObj(x: {a: number, ...}) { /* ... */ }16acceptsObj(o); // Works!17acceptsObj(foo); // Error!
17:12-17:14: Cannot call `acceptsObj` with `foo` bound to `x` because `Foo` [1] is not a subtype of object type [2]. Class instances are not subtypes of object types; consider rewriting object type [2] as an interface. [class-object-subtyping]

Unlike objects, interfaces cannot be exact, as they can always have other, unknown properties.

Interface Syntax

Interfaces are created using the keyword interface followed by its name and a block which contains the body of the type definition.

1interface MyInterface {2  // ...3}

The syntax of the block matches the syntax of object types.

Interface Methods

You can add methods to interfaces following the same syntax as class methods. Any this parameters you provide are also subject to the same restrictions as class methods.

1interface MyInterface {2  method(value: string): number;3}

Also like class methods, interface methods must also remain bound to the interface on which they were defined.

You can define overloaded methods by declaring the same method name multiple times with different type signatures:

1interface MyInterface {2  method(value: string): string;3  method(value: boolean): boolean;4}5
6function func(a: MyInterface) {7  const x: string = a.method('hi'); // Works!8  const y: boolean = a.method(true); // Works!9
10  const z: boolean = a.method('hi'); // Error!
11}
10:22-10:35: Cannot assign `a.method(...)` to `z` because string [1] is incompatible with boolean [2]. [incompatible-type]

Interface Properties

You can add properties to interfaces following the same syntax as class properties:

1interface MyInterface {2  property: string;3}

Interface properties can be optional as well:

1interface MyInterface {2  property?: string;3}

Interfaces as maps

You can create indexer properties the same way as with objects:

1interface MyInterface {2  [key: string]: number;3}

Interface Generics

Interfaces can also have their own generics:

1interface MyInterface<A, B, C> {2  property: A;3  method(val: B): C;4}

Interface generics are parameterized. When you use an interface you need to pass parameters for each of its generics:

1interface MyInterface<A, B, C> {2  foo: A;3  bar: B;4  baz: C;5}6
7const val: MyInterface<number, boolean, string> = {8  foo: 1,9  bar: true,10  baz: 'three',11};

Interface property variance (read-only and write-only)

Interface properties are invariant by default. But you can add modifiers to make them covariant (read-only) or contravariant (write-only).

1interface MyInterface {2  +covariant: number;     // read-only3  -contravariant: number; // write-only4}

Covariant (read-only) properties on interfaces

You can make a property covariant by adding a plus symbol + in front of the property name:

1interface MyInterface {2  +readOnly: number | string;3}

This allows you to pass a more specific type in place of that property:

1interface Invariant {2  property: number | string;3}4interface Covariant {5  +readOnly: number | string;6}7
8const x: {property: number} = {property: 42};9const y: {readOnly: number} = {readOnly: 42};10
11const value1: Invariant = x; // Error!
12const value2: Covariant = y; // Works
11:27-11:27: Cannot assign `x` to `value1` because string [1] is incompatible with number [2] in property `property`. This property is invariantly typed. See https://flow.org/en/docs/faq/#why-cant-i-pass-a-string-to-a-function-that-takes-a-string-number. [incompatible-type]

Because of how covariance works, covariant properties also become read-only when used. Which can be useful over normal properties.

1interface Invariant {2  property: number | string;3}4interface Covariant {5  +readOnly: number | string;6}7
8function func1(value: Invariant) {9  value.property;        // Works!10  value.property = 3.14; // Works!11}12
13function func2(value: Covariant) {14  value.readOnly;        // Works!15  value.readOnly = 3.14; // Error!
16}
15:9-15:16: Cannot assign `3.14` to `value.readOnly` because property `readOnly` is not writable. [cannot-write]

Contravariant (write-only) properties on interfaces

You can make a property contravariant by adding a minus symbol - in front of the property name.

1interface InterfaceName {2  -writeOnly: number;3}

This allows you to pass a less specific type in place of that property.

1interface Invariant {2  property: number;3}4interface Contravariant {5  -writeOnly: number;6}7
8const numberOrString = Math.random() > 0.5 ? 42 : 'forty-two';9
10const value1: Invariant     = {property: numberOrString};  // Error!
11const value2: Contravariant = {writeOnly: numberOrString}; // Works!
10:42-10:55: Cannot assign object literal to `value1` because string [1] is incompatible with number [2] in property `property`. [incompatible-type]

Because of how contravariance works, contravariant properties also become write-only when used. Which can be useful over normal properties.

1interface Invariant {2  property: number;3}4interface Contravariant {5  -writeOnly: number;6}7
8function func1(value: Invariant) {9  value.property;        // Works!10  value.property = 3.14; // Works!11}12
13function func2(value: Contravariant) {14  value.writeOnly;        // Error!
15 value.writeOnly = 3.14; // Works!16}
14:9-14:17: Cannot get `value.writeOnly` because property `writeOnly` is not readable. [cannot-read]