Skip to main content

Interfaces

Interfaces declare the structure a class or object must satisfy without requiring a specific class identity. Because classes in Flow are nominally typed, two classes with identical members are not interchangeable — interfaces provide structural typing to bridge that gap.

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: Foo = new Bar(); // Error!incompatible-typeCannot assign new Bar() to foo because Bar [1] is incompatible with Foo [2].14
15const foo2: Serializable = new Foo(); // Works!16const bar2: Serializable = new Bar(); // Works!

When to use this

Use interfaces over object types when you need to accept both class instances and plain objects with the same shape. Use interfaces over classes when you want structural compatibility — any value with matching properties is assignable, regardless of which class it was constructed from.

Interfaces for instances and objects

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!class-object-subtypingCannot 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.

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

Implementing interfaces

You can 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!incompatible-typeCannot implement Serializable [1] with Bar because in property serialize > the return value: number [2] is incompatible with string [3].11}

You can also use implements with multiple interfaces.

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

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.

Anonymous interfaces

You can also declare an anonymous interface inline:

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

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!incompatible-typeCannot assign a.method(...) to z because string [1] is incompatible with boolean [2].11}

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!incompatible-typeCannot assign x to value1 because in property property: number [1] is not exactly the same as number | string [2].12const value2: Covariant = y; // Works

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!cannot-writeCannot assign 3.14 to value.readOnly because property readOnly is not writable.16}

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!incompatible-typeCannot assign object literal to value1 because in property property: string [1] is incompatible with number [2].11const value2: Contravariant = {writeOnly: numberOrString}; // Works!

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!cannot-readCannot get value.writeOnly because property writeOnly is not readable.15  value.writeOnly = 3.14; // Works!16}

See Also

  • Classes — nominally typed values that can implement interfaces
  • Objects — structurally typed object types, which cannot describe class instances
  • Nominal & Structural Typing — the difference between name-based and shape-based typing
  • Variance — covariant (read-only) and contravariant (write-only) properties